diff --git a/README.md b/README.md index 49d4b81..2f39258 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ the Metal kernels and small native integration layers are adapted from sampling, cancellation, idle unload, and a bounded resident session pool. Supported acceleration includes SSD expert streaming, DeepSeek legacy MTP, DSpark, GLM MTP, directional steering, and model-specific memory admission. +- **DeepSeek V4 Flash Vision Experimental.** Model Manager offers the + Vision Experimental main model, required image encoder, and matching DSpark + drafter. Image input works in chat, tool results, and the HTTP APIs, with + image-aware KV reuse. The 0731 drafter is not interchangeable with this model. - **Local HTTP APIs.** A configurable listener on `127.0.0.1:4000` exposes Models, Chat Completions, Completions, Anthropic Messages, and Responses APIs with streaming, tool calls, usage accounting, cancellation, and prefix-aware diff --git a/docs/issue100-vision/README.md b/docs/issue100-vision/README.md new file mode 100644 index 0000000..8f9adb2 --- /dev/null +++ b/docs/issue100-vision/README.md @@ -0,0 +1,129 @@ +# Issue #100: DeepSeek V4 Flash Vision Experimental + +The catalog ID is `deepseek-v4-flash-vision-exp`. The main model and image +encoder are required together, including for text-only use; DSpark uses the +separate Vision-Exp support file. Model Manager verifies both hashes and GGUF +metadata before making the model available. Existing 0731 files remain separate. +The session database migration accepts the new model without changing existing +session IDs or content. + +## Reference and architecture + +The behavioral oracle is DS4 commit +`0aaea5a238fb41a35106a551e73c8409dfb751ac`. This is existing DS4 functionality; +MTPLX is the Qwen oracle and has no corresponding DeepSeek implementation. +The three GGUF artifacts are pinned by size and SHA256 in `src/model.rs`, from +`antirez/deepseek-v4-gguf` revision +`f71f23d552d664e523b422157b2befbf74040380`. Their required source checkpoint is +`deepseek-ai/DeepSeek-V4-Flash-Vision-Exp` revision +`e46e16bf6035c6f317eb2ac7458eb0362926d402`. + +All image decoding, resizing, patch construction, encoder graph execution, +prompt layout, attention-mask construction, model loading, and cache ownership +are Rust. Objective-C only binds Metal buffers and dispatches GPU primitives. +No DS4 C application or Python model implementation is linked, compiled into, +or invoked by the product. The independent Python comparison in +`tools/issue100-vision-reference.py` is test-only. + +| Requirement / DS4 behavior | Implementation | +| --- | --- | +| Distinct checkpoint and drafter | Three catalog artifacts; strict revision, variant, tensor inventory, and RMS epsilon checks; wrong drafter/encoder rejection | +| Image preprocessing | EXIF orientation; decoded RGB identity; DS4 bicubic resize, centered padding, 14-pixel channel-major patches, minimum pixels and 381-token pre-alignment budget | +| Vision tower | 32 Rust-dispatched ViT blocks, 2-D RoPE, DS4 BF16 rounding points, 3x3 aligner reorder, GELU projection to 4096 dimensions | +| Image prompt | Position-dependent leading pads, paired-row permutation, newline/pad/end sentinel vectors, synthetic token kinds, whole-image prefill chunks | +| Visual language-model execution | Image embeddings broadcast to all hyperconnection streams, bidirectional image-window attention, causal compressed keys with indexer masks, visual MoE routing biases and ordinary text hash routing | +| Continued conversations | Historical image identity and positions retained with the exact generated token frontier; new images laid out at their actual appended positions | +| Cache safety | Decoded-pixel fingerprints, position/count checks, changed/reordered-image invalidation, resident-session ownership, checkpoint serialization and validation | +| App / API use | Shared model selector, model manager, image/tool-result input, API model ID, reasoning modes, statistics, configuration, and evaluator support | + +DeepSeek image identity in DS4Server's disk checkpoints is a DS4Server storage +extension. It validates every serialized image span against the synthetic token +stream before installing the restored state. Other model checkpoint formats +and payloads are unchanged. + +## Numerical and regression evidence + +The Rust encoder was reviewed operation by operation against +`ds4_gpu_deepseek4_vision_encode`. The reused BF16 matrix bridge is byte-identical +at the function level (SHA256 +`621818731c2d43989a8fd08f186d1054ecdb49a91cb1453591f07bc2613d3fef`). +`metal/glm53_bf16.metal` is byte-identical to that DS4 revision (SHA256 +`27d93dcfe81b09f93ed65cd986aaa0b9c1ca1df7fc43e58c15da533284d50db5`). +The shared vision kernels used here also match; the reference's unrelated GLM +merger-layernorm barrier change is outside the DeepSeek graph. + +The independent published DeepSeek PyTorch encoder comparison on the fixed red +square produces 100x4096 aligned values, cosine similarity 0.99500793, RMSE +0.00568503, and maximum absolute difference 0.10229492. This is not a bitwise +DS4 oracle comparison: the published Python graph uses BF16 module operations, +while DS4 accepts FP32 patches and specifies its own BF16 rounding boundaries. +DS4's operation order and rounding are retained in the Rust graph. + +Focused checks include DS4's published image-layout and attention-bound vectors, +image-size limits, real sidecar tensor validation, wrong-checkpoint rejection, +all DeepSeek reasoning modes in image continuations, mixed text/image routing +with exact selected experts and analytical normalized weights, and raw/mixed +attention over a wrapped KV ring with independently computed visible-key means. +The existing real-model 0731 resident-session and GLM 5.3 verifier tests pass. + +## Reproducing the checks + +Download the three catalog files at the artifact revision above into +`local-eval-results/issue100-artifacts/`. The ignored `issue100_managed_intake` +test hard-links these into `local-eval-results/issue100-managed/`, runs the real +SHA256/intake pipeline, and writes normal verification markers only after success. + +```sh +cargo test --all-features issue100 +cargo test --all-features issue100_checkpoint_metadata -- --ignored --nocapture +cargo test --all-features issue100_visual_text -- --ignored --nocapture +cargo test --all-features issue100_managed_intake -- --ignored --nocapture +``` + +Run Metal tests serially in separate processes under `test-supervisor`, with the +child output redirected to a separate log. Copy the compiled test binary outside +`target/` before starting a longer test. The encoder accepts +`DS4_ISSUE100_IMAGE` and `DS4_ISSUE100_ENCODER_OUTPUT` (dump prefix); its filter is +`issue100_encoder_real_weights`. The small primitive filter is +`issue100_visual_routing_and_attention`. The production generation/cache test +filter is `issue100_full_model_images_cache_and_dspark` and uses the committed +PNG fixtures here. `DS4_ISSUE100_KEEP_VISION=1` selects persistent encoder +residency; `DS4_ISSUE100_SSD=1` selects a 4096-expert SSD cache. + +The full-model test requires the intake test first and checks real image answers, +target-only/DSpark agreement with actual drafting, warm/disk continuation, +same-token-layout image replacement, multiple images, tool images, and text-only +use after image requests. All three isolated runs passed on the M5 Max, using +the real managed artifacts after successful SHA256 and metadata intake. + + +## Final isolated validation + +| Runtime mode | Result | Duration | DSpark drafted / accepted | +| --- | --- | --- | --- | +| Resident, transient encoder | Pass | 15.10 s | 15 / 10 | +| Resident, persistent encoder | Pass | 15.94 s | 15 / 10 | +| SSD, 4096-expert cache and preload | Pass | 31.46 s | 30 / 0 | + +Each fresh process checked red-square and blue-circle recognition, image order, +new user and tool images, changed images with identical synthetic prompt tokens, +warm and disk-restored continuation, exact checkpoint logits, and text-only use +after images. DSpark output matched target-only output within each runtime mode; +the resident and SSD answers use different wording, so these receipts do not +claim token equivalence between storage modes. Zero accepted drafts in the SSD +fixture is a measured result, not a disabled drafter. + +The SSD log retains 129 existing mapped-range diagnostics (three per layer) from +the GPU-copy hotlist preload attempt. The existing Rust loader then successfully +seeds through the host-loading primitive after finishing GPU commands. Both +preload primitives exist in the DS4 reference; this change retains the existing +DS4Server fallback. These are not image-encoder failures. Resource logs are +process accounting and do not measure total file-backed GPU residency. + +All commit gates passed: rustfmt, Clippy with warnings denied, signed app bundle, +and all-feature tests (323 application tests plus eight tool/supervisor tests; +247 hardware/artifact-dependent tests are ignored by that generic suite). +The relevant ignored tests were run explicitly as documented above. The bundle +contains the new Metal kernel and its DS4 license. Logs alongside this document +record intake, encoder/primitive checks, family regression controls, the three +full-model runs, and the commit gates. diff --git a/docs/issue100-vision/bundle.log b/docs/issue100-vision/bundle.log new file mode 100644 index 0000000..8b56eb5 --- /dev/null +++ b/docs/issue100-vision/bundle.log @@ -0,0 +1,8 @@ +cargo build --release --all-targets --all-features + Compiling ds4-server v0.1.0 (/Users/gb/Projects/DS4Server) + Finished `release` profile [optimized] target(s) in 43.29s +warning: the following packages contain code that will be rejected by a future version of Rust: block v0.1.6 +note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1` +cargo packager --release --formats app +codesign --force --deep --sign - --timestamp=none target/release/DS4Server.app +target/release/DS4Server.app: replacing existing signature diff --git a/docs/issue100-vision/clippy.log b/docs/issue100-vision/clippy.log new file mode 100644 index 0000000..804600e --- /dev/null +++ b/docs/issue100-vision/clippy.log @@ -0,0 +1,4 @@ + Checking ds4-server v0.1.0 (/Users/gb/Projects/DS4Server) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.60s +warning: the following packages contain code that will be rejected by a future version of Rust: block v0.1.6 +note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1` diff --git a/docs/issue100-vision/deepseek-control.log b/docs/issue100-vision/deepseek-control.log new file mode 100644 index 0000000..ac1e1b4 --- /dev/null +++ b/docs/issue100-vision/deepseek-control.log @@ -0,0 +1,10 @@ + +running 1 test +test engine::metal::tests::resident_multi_session_switching_preserves_each_kv_frontier ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal model views created in 1.615 ms, residency requested in 9081.153 ms, warmup 3.442 ms (mapped 82697.67 MiB from offset 5.09 MiB) +ds4: Metal mapped mmaped model as 2 overlapping shared buffers +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 568 filtered out; finished in 11.19s diff --git a/docs/issue100-vision/deepseek-tokenizer-control.log b/docs/issue100-vision/deepseek-tokenizer-control.log new file mode 100644 index 0000000..0c136d3 --- /dev/null +++ b/docs/issue100-vision/deepseek-tokenizer-control.log @@ -0,0 +1,5 @@ + +running 1 test +test engine::tokenizer::tests::ds4_chat_matches_original_session_tokens ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 0.14s diff --git a/docs/issue100-vision/encoder-resources.jsonl b/docs/issue100-vision/encoder-resources.jsonl new file mode 100644 index 0000000..7585bb6 --- /dev/null +++ b/docs/issue100-vision/encoder-resources.jsonl @@ -0,0 +1,45 @@ +{"event":"test_supervisor_started","pid":13017,"limit_bytes":17179869184,"start_seconds":60.0,"idle_seconds":120.0,"sample_ms":100} +{"event":"test_resource_sample","elapsed_ms":0,"usage":{"cpu_time_ns":163666,"resident_bytes":688128,"physical_bytes":213064,"peak_physical_bytes":213064,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":101,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":205,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":306,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":407,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":518,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":626,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":733,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":835,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":943,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1052,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1163,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1273,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1374,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1482,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1592,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1697,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1802,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1908,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2018,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2119,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2224,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2334,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2441,"usage":{"cpu_time_ns":2659875,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1593344,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2542,"usage":{"cpu_time_ns":31532500,"resident_bytes":30605312,"physical_bytes":11207424,"peak_physical_bytes":11207424,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2648,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2751,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2855,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2958,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3068,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3178,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3288,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3393,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3500,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3607,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3717,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3818,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3924,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4029,"usage":{"cpu_time_ns":31631291,"resident_bytes":30621696,"physical_bytes":11223808,"peak_physical_bytes":12288768,"disk_read_bytes":2285568,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4134,"usage":{"cpu_time_ns":79446208,"resident_bytes":61718528,"physical_bytes":145966496,"peak_physical_bytes":145966496,"disk_read_bytes":2285568,"disk_write_bytes":4620288}} +{"event":"test_resource_sample","elapsed_ms":4244,"usage":{"cpu_time_ns":189471500,"resident_bytes":62750720,"physical_bytes":146769312,"peak_physical_bytes":146769312,"disk_read_bytes":2285568,"disk_write_bytes":4620288}} +{"event":"test_resource_sample","elapsed_ms":4349,"usage":{"cpu_time_ns":265315666,"resident_bytes":67895296,"physical_bytes":213583264,"peak_physical_bytes":213583264,"disk_read_bytes":2613248,"disk_write_bytes":4833280}} +{"event":"test_resource_sample","elapsed_ms":4451,"usage":{"cpu_time_ns":265315666,"resident_bytes":67895296,"physical_bytes":213583264,"peak_physical_bytes":213583264,"disk_read_bytes":2613248,"disk_write_bytes":4833280}} +{"event":"test_resource_summary","elapsed_ms":4560,"peak":{"cpu_time_ns":265315666,"resident_bytes":67895296,"physical_bytes":213583264,"peak_physical_bytes":213583264,"disk_read_bytes":2613248,"disk_write_bytes":4833280},"error":null} diff --git a/docs/issue100-vision/encoder.log b/docs/issue100-vision/encoder.log new file mode 100644 index 0000000..f695a83 --- /dev/null +++ b/docs/issue100-vision/encoder.log @@ -0,0 +1,11 @@ + +running 1 test +test engine::metal::deepseek_vision::tests::issue100_encoder_real_weights ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal model views created in 0.039 ms, residency requested in 6.538 ms, warmup 3.368 ms (mapped 889.64 MiB from offset 0.02 MiB) +ds4: Metal mapped mmaped model as 1 overlapping shared buffers +{"grid":[28,28],"aligned_grid":[10,10],"tokens":100,"width":224,"height":224,"values":409600} +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 563 filtered out; finished in 2.01s diff --git a/docs/issue100-vision/full-persistent-resources.jsonl b/docs/issue100-vision/full-persistent-resources.jsonl new file mode 100644 index 0000000..cb2cd70 --- /dev/null +++ b/docs/issue100-vision/full-persistent-resources.jsonl @@ -0,0 +1,149 @@ +{"event":"test_supervisor_started","pid":37642,"limit_bytes":120259084288,"start_seconds":60.0,"idle_seconds":120.0,"sample_ms":100} +{"event":"test_resource_sample","elapsed_ms":0,"usage":{"cpu_time_ns":158791,"resident_bytes":655360,"physical_bytes":180296,"peak_physical_bytes":213064,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":110,"usage":{"cpu_time_ns":86772708,"resident_bytes":61030400,"physical_bytes":39846512,"peak_physical_bytes":39846512,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":220,"usage":{"cpu_time_ns":174636750,"resident_bytes":108462080,"physical_bytes":68764608,"peak_physical_bytes":68764608,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":325,"usage":{"cpu_time_ns":281313375,"resident_bytes":108462080,"physical_bytes":68764608,"peak_physical_bytes":68764608,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":428,"usage":{"cpu_time_ns":383873000,"resident_bytes":108462080,"physical_bytes":68764608,"peak_physical_bytes":68764608,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":537,"usage":{"cpu_time_ns":492457791,"resident_bytes":108462080,"physical_bytes":68764608,"peak_physical_bytes":68764608,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":647,"usage":{"cpu_time_ns":602852916,"resident_bytes":109133824,"physical_bytes":187646912,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":757,"usage":{"cpu_time_ns":712857208,"resident_bytes":109133824,"physical_bytes":187646912,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":859,"usage":{"cpu_time_ns":812860541,"resident_bytes":109133824,"physical_bytes":187548608,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":969,"usage":{"cpu_time_ns":922864833,"resident_bytes":109133824,"physical_bytes":187548608,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1075,"usage":{"cpu_time_ns":1022868333,"resident_bytes":109133824,"physical_bytes":187548608,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1185,"usage":{"cpu_time_ns":1059079041,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1292,"usage":{"cpu_time_ns":1059079041,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1402,"usage":{"cpu_time_ns":1068676208,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1511,"usage":{"cpu_time_ns":1138948708,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1621,"usage":{"cpu_time_ns":1248951791,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1724,"usage":{"cpu_time_ns":1348956625,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1834,"usage":{"cpu_time_ns":1458987666,"resident_bytes":109166592,"physical_bytes":187564992,"peak_physical_bytes":187646912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1944,"usage":{"cpu_time_ns":1576030458,"resident_bytes":128204800,"physical_bytes":202311240,"peak_physical_bytes":202311240,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2054,"usage":{"cpu_time_ns":1678444500,"resident_bytes":132841472,"physical_bytes":268912248,"peak_physical_bytes":268912248,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2162,"usage":{"cpu_time_ns":1683235708,"resident_bytes":135757824,"physical_bytes":268977784,"peak_physical_bytes":268977784,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2270,"usage":{"cpu_time_ns":1787228333,"resident_bytes":136052736,"physical_bytes":270878328,"peak_physical_bytes":270878328,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2380,"usage":{"cpu_time_ns":1792551041,"resident_bytes":137576448,"physical_bytes":351028904,"peak_physical_bytes":351028904,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2490,"usage":{"cpu_time_ns":1798559166,"resident_bytes":138182656,"physical_bytes":371295912,"peak_physical_bytes":371295912,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2601,"usage":{"cpu_time_ns":1804472791,"resident_bytes":138674176,"physical_bytes":384419496,"peak_physical_bytes":384419496,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2711,"usage":{"cpu_time_ns":1809852125,"resident_bytes":139116544,"physical_bytes":396428968,"peak_physical_bytes":396428968,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2821,"usage":{"cpu_time_ns":1816155541,"resident_bytes":139624448,"physical_bytes":409552552,"peak_physical_bytes":409552552,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2931,"usage":{"cpu_time_ns":1873384375,"resident_bytes":140836864,"physical_bytes":434636456,"peak_physical_bytes":434636456,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3041,"usage":{"cpu_time_ns":1981844875,"resident_bytes":154206208,"physical_bytes":434914984,"peak_physical_bytes":434914984,"disk_read_bytes":16384,"disk_write_bytes":25169920}} +{"event":"test_resource_sample","elapsed_ms":3152,"usage":{"cpu_time_ns":2037590041,"resident_bytes":160645120,"physical_bytes":306103976,"peak_physical_bytes":447579864,"disk_read_bytes":16384,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":3259,"usage":{"cpu_time_ns":2126845083,"resident_bytes":161169408,"physical_bytes":318293672,"peak_physical_bytes":447579864,"disk_read_bytes":16384,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":3369,"usage":{"cpu_time_ns":2231828375,"resident_bytes":147636224,"physical_bytes":260523712,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3475,"usage":{"cpu_time_ns":2338136333,"resident_bytes":162742272,"physical_bytes":255903400,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3585,"usage":{"cpu_time_ns":2409057041,"resident_bytes":164610048,"physical_bytes":320128680,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3696,"usage":{"cpu_time_ns":2417197916,"resident_bytes":150568960,"physical_bytes":375146200,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3806,"usage":{"cpu_time_ns":2422607208,"resident_bytes":151011328,"physical_bytes":387172056,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3916,"usage":{"cpu_time_ns":2428629583,"resident_bytes":151502848,"physical_bytes":400279256,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4022,"usage":{"cpu_time_ns":2433951458,"resident_bytes":151945216,"physical_bytes":411862744,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4125,"usage":{"cpu_time_ns":2439699666,"resident_bytes":152387584,"physical_bytes":442828504,"peak_physical_bytes":447579864,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4235,"usage":{"cpu_time_ns":2876701875,"resident_bytes":195395584,"physical_bytes":360859448,"peak_physical_bytes":484361992,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4342,"usage":{"cpu_time_ns":3007532875,"resident_bytes":205291520,"physical_bytes":482150200,"peak_physical_bytes":484361992,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4449,"usage":{"cpu_time_ns":3332647875,"resident_bytes":208683008,"physical_bytes":484591416,"peak_physical_bytes":484591416,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4559,"usage":{"cpu_time_ns":3765325791,"resident_bytes":208961536,"physical_bytes":484869944,"peak_physical_bytes":484869944,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4669,"usage":{"cpu_time_ns":3864230375,"resident_bytes":209551360,"physical_bytes":484738872,"peak_physical_bytes":484886328,"disk_read_bytes":24576,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4779,"usage":{"cpu_time_ns":3967922875,"resident_bytes":205324288,"physical_bytes":375228192,"peak_physical_bytes":497403752,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4881,"usage":{"cpu_time_ns":4068514458,"resident_bytes":221200384,"physical_bytes":391120696,"peak_physical_bytes":497403752,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4991,"usage":{"cpu_time_ns":4091918750,"resident_bytes":221921280,"physical_bytes":447317816,"peak_physical_bytes":497403752,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5099,"usage":{"cpu_time_ns":4102419666,"resident_bytes":206536704,"physical_bytes":506021688,"peak_physical_bytes":506021688,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5209,"usage":{"cpu_time_ns":4107575708,"resident_bytes":206979072,"physical_bytes":518031160,"peak_physical_bytes":518031160,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5319,"usage":{"cpu_time_ns":4113264541,"resident_bytes":207470592,"physical_bytes":531171128,"peak_physical_bytes":531171128,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5429,"usage":{"cpu_time_ns":4118395625,"resident_bytes":207912960,"physical_bytes":542754616,"peak_physical_bytes":542754616,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5539,"usage":{"cpu_time_ns":4127693000,"resident_bytes":208846848,"physical_bytes":572409656,"peak_physical_bytes":572409656,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5649,"usage":{"cpu_time_ns":4559910291,"resident_bytes":211697664,"physical_bytes":488245096,"peak_physical_bytes":599164728,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5759,"usage":{"cpu_time_ns":4996436541,"resident_bytes":212369408,"physical_bytes":488359808,"peak_physical_bytes":599164728,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5870,"usage":{"cpu_time_ns":5435416416,"resident_bytes":212992000,"physical_bytes":488458112,"peak_physical_bytes":599164728,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5980,"usage":{"cpu_time_ns":5499016208,"resident_bytes":213499904,"physical_bytes":488458112,"peak_physical_bytes":599164728,"disk_read_bytes":32768,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":6090,"usage":{"cpu_time_ns":5606456291,"resident_bytes":227901440,"physical_bytes":488458112,"peak_physical_bytes":599164728,"disk_read_bytes":40960,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":6202,"usage":{"cpu_time_ns":5715925250,"resident_bytes":227901440,"physical_bytes":488458112,"peak_physical_bytes":599164728,"disk_read_bytes":45056,"disk_write_bytes":130789376}} +{"event":"test_resource_sample","elapsed_ms":6309,"usage":{"cpu_time_ns":5720009125,"resident_bytes":228179968,"physical_bytes":502368128,"peak_physical_bytes":599164728,"disk_read_bytes":45056,"disk_write_bytes":130789376}} +{"event":"test_resource_sample","elapsed_ms":6419,"usage":{"cpu_time_ns":5724536500,"resident_bytes":228179968,"physical_bytes":503039872,"peak_physical_bytes":599164728,"disk_read_bytes":45056,"disk_write_bytes":130789376}} +{"event":"test_resource_sample","elapsed_ms":6529,"usage":{"cpu_time_ns":5732569833,"resident_bytes":228196352,"physical_bytes":503039872,"peak_physical_bytes":599164728,"disk_read_bytes":45056,"disk_write_bytes":130789376}} +{"event":"test_resource_sample","elapsed_ms":6639,"usage":{"cpu_time_ns":6201525250,"resident_bytes":228835328,"physical_bytes":491308928,"peak_physical_bytes":599164728,"disk_read_bytes":45056,"disk_write_bytes":130789376}} +{"event":"test_resource_sample","elapsed_ms":6749,"usage":{"cpu_time_ns":6311372583,"resident_bytes":228835328,"physical_bytes":491308928,"peak_physical_bytes":599164728,"disk_read_bytes":45056,"disk_write_bytes":130789376}} +{"event":"test_resource_sample","elapsed_ms":6859,"usage":{"cpu_time_ns":6415947833,"resident_bytes":222134272,"physical_bytes":385697592,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":6961,"usage":{"cpu_time_ns":6494033333,"resident_bytes":223510528,"physical_bytes":448546592,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":7063,"usage":{"cpu_time_ns":6496621833,"resident_bytes":223526912,"physical_bytes":405948216,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":7173,"usage":{"cpu_time_ns":6501995166,"resident_bytes":223526912,"physical_bytes":429262648,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":7275,"usage":{"cpu_time_ns":6593164875,"resident_bytes":224968704,"physical_bytes":476743504,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":7381,"usage":{"cpu_time_ns":6986154666,"resident_bytes":227557376,"physical_bytes":479479632,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":7491,"usage":{"cpu_time_ns":7096023166,"resident_bytes":227688448,"physical_bytes":479479632,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":158916608}} +{"event":"test_resource_sample","elapsed_ms":7601,"usage":{"cpu_time_ns":7201462833,"resident_bytes":228966400,"physical_bytes":479479632,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":7711,"usage":{"cpu_time_ns":7219993875,"resident_bytes":230834176,"physical_bytes":543704912,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":7821,"usage":{"cpu_time_ns":7321288833,"resident_bytes":228982784,"physical_bytes":450332496,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":7931,"usage":{"cpu_time_ns":7330549833,"resident_bytes":236273664,"physical_bytes":518342480,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8042,"usage":{"cpu_time_ns":7342040750,"resident_bytes":233537536,"physical_bytes":577849168,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8152,"usage":{"cpu_time_ns":7350480041,"resident_bytes":234192896,"physical_bytes":578504528,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8262,"usage":{"cpu_time_ns":7360057041,"resident_bytes":234913792,"physical_bytes":579225424,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8372,"usage":{"cpu_time_ns":7369511708,"resident_bytes":235667456,"physical_bytes":579979088,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8482,"usage":{"cpu_time_ns":7377769000,"resident_bytes":236339200,"physical_bytes":580634448,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8592,"usage":{"cpu_time_ns":7819415708,"resident_bytes":236404736,"physical_bytes":457819984,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8702,"usage":{"cpu_time_ns":7929061291,"resident_bytes":236421120,"physical_bytes":457836368,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":187043840}} +{"event":"test_resource_sample","elapsed_ms":8812,"usage":{"cpu_time_ns":8034578750,"resident_bytes":248725504,"physical_bytes":457836368,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":8922,"usage":{"cpu_time_ns":8101839000,"resident_bytes":250855424,"physical_bytes":522291024,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9033,"usage":{"cpu_time_ns":8153384833,"resident_bytes":249004032,"physical_bytes":458082128,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9143,"usage":{"cpu_time_ns":8210025625,"resident_bytes":250888192,"physical_bytes":522323792,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9253,"usage":{"cpu_time_ns":8270070583,"resident_bytes":249036800,"physical_bytes":458114896,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9359,"usage":{"cpu_time_ns":8317796000,"resident_bytes":250920960,"physical_bytes":522356560,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9469,"usage":{"cpu_time_ns":8322483791,"resident_bytes":254771200,"physical_bytes":480429880,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9579,"usage":{"cpu_time_ns":8326288208,"resident_bytes":254771200,"physical_bytes":480429880,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9689,"usage":{"cpu_time_ns":8336750625,"resident_bytes":255180800,"physical_bytes":582321976,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9799,"usage":{"cpu_time_ns":8346214125,"resident_bytes":255901696,"physical_bytes":571262800,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":9909,"usage":{"cpu_time_ns":8355466000,"resident_bytes":256655360,"physical_bytes":572016464,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":10019,"usage":{"cpu_time_ns":8364417875,"resident_bytes":257376256,"physical_bytes":572737360,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":10125,"usage":{"cpu_time_ns":8373669666,"resident_bytes":258129920,"physical_bytes":573572944,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":10235,"usage":{"cpu_time_ns":8823157958,"resident_bytes":258326528,"physical_bytes":460605264,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":10342,"usage":{"cpu_time_ns":8929590083,"resident_bytes":258326528,"physical_bytes":460605264,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":227495936}} +{"event":"test_resource_sample","elapsed_ms":10452,"usage":{"cpu_time_ns":9035189583,"resident_bytes":223461376,"physical_bytes":358303568,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":10559,"usage":{"cpu_time_ns":9140813375,"resident_bytes":235307008,"physical_bytes":361875280,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":10660,"usage":{"cpu_time_ns":9189294625,"resident_bytes":237404160,"physical_bytes":426297168,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":10770,"usage":{"cpu_time_ns":9198647541,"resident_bytes":223264768,"physical_bytes":482707280,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":10880,"usage":{"cpu_time_ns":9203889041,"resident_bytes":223707136,"physical_bytes":494290768,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":10990,"usage":{"cpu_time_ns":9209785166,"resident_bytes":224215040,"physical_bytes":507414352,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11101,"usage":{"cpu_time_ns":9215004875,"resident_bytes":224657408,"physical_bytes":519423824,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11211,"usage":{"cpu_time_ns":9220738000,"resident_bytes":225099776,"physical_bytes":549930832,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11321,"usage":{"cpu_time_ns":9674345333,"resident_bytes":228573184,"physical_bytes":500369280,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11425,"usage":{"cpu_time_ns":9943252875,"resident_bytes":229130240,"physical_bytes":504547200,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11532,"usage":{"cpu_time_ns":10150432208,"resident_bytes":229703680,"physical_bytes":504596352,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11642,"usage":{"cpu_time_ns":10260039750,"resident_bytes":229703680,"physical_bytes":504596352,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":281427968}} +{"event":"test_resource_sample","elapsed_ms":11753,"usage":{"cpu_time_ns":10365728416,"resident_bytes":250511360,"physical_bytes":421431120,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":11863,"usage":{"cpu_time_ns":10468843916,"resident_bytes":251428864,"physical_bytes":476284752,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":11973,"usage":{"cpu_time_ns":10485306541,"resident_bytes":253067264,"physical_bytes":476284752,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12083,"usage":{"cpu_time_ns":10580108791,"resident_bytes":251445248,"physical_bytes":476301136,"peak_physical_bytes":599164728,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12193,"usage":{"cpu_time_ns":10590163125,"resident_bytes":227557376,"physical_bytes":625657704,"peak_physical_bytes":625657704,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12303,"usage":{"cpu_time_ns":10600883083,"resident_bytes":228573184,"physical_bytes":647989096,"peak_physical_bytes":647989096,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12413,"usage":{"cpu_time_ns":10611199541,"resident_bytes":229621760,"physical_bytes":658081640,"peak_physical_bytes":658081640,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12524,"usage":{"cpu_time_ns":10621232500,"resident_bytes":230637568,"physical_bytes":667715432,"peak_physical_bytes":667715432,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12634,"usage":{"cpu_time_ns":10631547416,"resident_bytes":231686144,"physical_bytes":677807976,"peak_physical_bytes":677807976,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12744,"usage":{"cpu_time_ns":10643259916,"resident_bytes":232718336,"physical_bytes":649594728,"peak_physical_bytes":686278504,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12854,"usage":{"cpu_time_ns":10652608916,"resident_bytes":233603072,"physical_bytes":673482600,"peak_physical_bytes":686278504,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":12963,"usage":{"cpu_time_ns":11113440875,"resident_bytes":237142016,"physical_bytes":508725096,"peak_physical_bytes":709068648,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":13073,"usage":{"cpu_time_ns":11310966750,"resident_bytes":237928448,"physical_bytes":517588840,"peak_physical_bytes":709068648,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":13182,"usage":{"cpu_time_ns":11583143708,"resident_bytes":238125056,"physical_bytes":517785448,"peak_physical_bytes":709068648,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":13292,"usage":{"cpu_time_ns":12074052916,"resident_bytes":238698496,"physical_bytes":520570728,"peak_physical_bytes":709068648,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":13402,"usage":{"cpu_time_ns":12183887500,"resident_bytes":238698496,"physical_bytes":520570728,"peak_physical_bytes":709068648,"disk_read_bytes":49152,"disk_write_bytes":307720192}} +{"event":"test_resource_sample","elapsed_ms":13512,"usage":{"cpu_time_ns":12287995458,"resident_bytes":264699904,"physical_bytes":520570728,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":13623,"usage":{"cpu_time_ns":12365533041,"resident_bytes":266616832,"physical_bytes":584812392,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":13730,"usage":{"cpu_time_ns":12406870416,"resident_bytes":268255232,"physical_bytes":584812392,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":13841,"usage":{"cpu_time_ns":12476579583,"resident_bytes":266633216,"physical_bytes":584828776,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":13951,"usage":{"cpu_time_ns":12525649333,"resident_bytes":231768064,"physical_bytes":404277072,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14061,"usage":{"cpu_time_ns":12634644083,"resident_bytes":230735872,"physical_bytes":397395792,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14171,"usage":{"cpu_time_ns":12645684541,"resident_bytes":232620032,"physical_bytes":461637456,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14281,"usage":{"cpu_time_ns":12654951833,"resident_bytes":233095168,"physical_bytes":545392464,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14391,"usage":{"cpu_time_ns":12660581375,"resident_bytes":233537536,"physical_bytes":556975952,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14501,"usage":{"cpu_time_ns":12666264500,"resident_bytes":233979904,"physical_bytes":568985424,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14609,"usage":{"cpu_time_ns":12671240625,"resident_bytes":234373120,"physical_bytes":579471184,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14719,"usage":{"cpu_time_ns":12676731250,"resident_bytes":234766336,"physical_bytes":608880464,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14829,"usage":{"cpu_time_ns":13145764291,"resident_bytes":238206976,"physical_bytes":514164584,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":14939,"usage":{"cpu_time_ns":13600598416,"resident_bytes":238780416,"physical_bytes":518326120,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":15049,"usage":{"cpu_time_ns":13675123958,"resident_bytes":239763456,"physical_bytes":518358888,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":345866240}} +{"event":"test_resource_sample","elapsed_ms":15159,"usage":{"cpu_time_ns":13781108666,"resident_bytes":262504448,"physical_bytes":523552616,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":372752384}} +{"event":"test_resource_sample","elapsed_ms":15270,"usage":{"cpu_time_ns":13834173625,"resident_bytes":233472000,"physical_bytes":502433616,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":372752384}} +{"event":"test_resource_sample","elapsed_ms":15375,"usage":{"cpu_time_ns":13837911416,"resident_bytes":233472000,"physical_bytes":526190416,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":372752384}} +{"event":"test_resource_sample","elapsed_ms":15485,"usage":{"cpu_time_ns":13847026875,"resident_bytes":234209280,"physical_bytes":576014160,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":372752384}} +{"event":"test_resource_sample","elapsed_ms":15596,"usage":{"cpu_time_ns":14333755000,"resident_bytes":237600768,"physical_bytes":578946896,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":372752384}} +{"event":"test_resource_sample","elapsed_ms":15706,"usage":{"cpu_time_ns":14443734750,"resident_bytes":237600768,"physical_bytes":578946896,"peak_physical_bytes":709068648,"disk_read_bytes":53248,"disk_write_bytes":372752384}} +{"event":"test_resource_sample","elapsed_ms":15816,"usage":{"cpu_time_ns":14541420333,"resident_bytes":219529216,"physical_bytes":299026184,"peak_physical_bytes":709068648,"disk_read_bytes":61440,"disk_write_bytes":387313664}} +{"event":"test_resource_sample","elapsed_ms":15926,"usage":{"cpu_time_ns":14651424000,"resident_bytes":219529216,"physical_bytes":299026184,"peak_physical_bytes":709068648,"disk_read_bytes":61440,"disk_write_bytes":387313664}} +{"event":"test_resource_summary","elapsed_ms":16036,"peak":{"cpu_time_ns":14651424000,"resident_bytes":268255232,"physical_bytes":677807976,"peak_physical_bytes":709068648,"disk_read_bytes":61440,"disk_write_bytes":387313664},"error":null} diff --git a/docs/issue100-vision/full-persistent.log b/docs/issue100-vision/full-persistent.log new file mode 100644 index 0000000..574cde0 --- /dev/null +++ b/docs/issue100-vision/full-persistent.log @@ -0,0 +1,20 @@ + +running 1 test +test engine::metal::deepseek_vision::tests::issue100_full_model_images_cache_and_dspark ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal model views created in 1.695 ms, residency requested in 461.052 ms, warmup 3.497 ms (mapped 82697.67 MiB from offset 5.09 MiB) +ds4: Metal mapped mmaped model as 2 overlapping shared buffers +ds4: Metal model views created in 0.143 ms, residency requested in 461.793 ms, warmup 0.639 ms (mapped 5711.67 MiB from offset 0.01 MiB) +ds4: Metal mapped mmaped model as 3 overlapping shared buffers +ds4: Metal model views created in 0.047 ms, residency requested in 833.434 ms, warmup 0.581 ms (mapped 889.64 MiB from offset 0.02 MiB) +ds4: Metal mapped mmaped model as 4 overlapping shared buffers +red: The image features a solid red square centered on a white background. +DSpark drafted=15 accepted=10 +blue: A solid blue circle centered on a white background. +pair: The first shape is red, and the second shape is blue. +tool image: The color in the image is blue. +issue100: production rendering, image recognition, multi-image/tool input, same-layout image invalidation, disk/warm KV and DSpark target equivalence passed +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 15.94s diff --git a/docs/issue100-vision/full-resident-resources.jsonl b/docs/issue100-vision/full-resident-resources.jsonl new file mode 100644 index 0000000..3cd3c0f --- /dev/null +++ b/docs/issue100-vision/full-resident-resources.jsonl @@ -0,0 +1,142 @@ +{"event":"test_supervisor_started","pid":37604,"limit_bytes":120259084288,"start_seconds":60.0,"idle_seconds":120.0,"sample_ms":100} +{"event":"test_resource_sample","elapsed_ms":0,"usage":{"cpu_time_ns":415125,"resident_bytes":180224,"physical_bytes":245952,"peak_physical_bytes":295032,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":101,"usage":{"cpu_time_ns":97345208,"resident_bytes":63045632,"physical_bytes":41222744,"peak_physical_bytes":41222744,"disk_read_bytes":126976,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":212,"usage":{"cpu_time_ns":161737875,"resident_bytes":108445696,"physical_bytes":68830144,"peak_physical_bytes":68830144,"disk_read_bytes":9883648,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":322,"usage":{"cpu_time_ns":271741791,"resident_bytes":108445696,"physical_bytes":68830144,"peak_physical_bytes":68830144,"disk_read_bytes":9883648,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":432,"usage":{"cpu_time_ns":391744500,"resident_bytes":108445696,"physical_bytes":68830144,"peak_physical_bytes":68830144,"disk_read_bytes":9883648,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":542,"usage":{"cpu_time_ns":501772208,"resident_bytes":108445696,"physical_bytes":68830144,"peak_physical_bytes":68830144,"disk_read_bytes":9883648,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":648,"usage":{"cpu_time_ns":599084208,"resident_bytes":109133824,"physical_bytes":187728832,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":758,"usage":{"cpu_time_ns":709088500,"resident_bytes":109133824,"physical_bytes":187728832,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":868,"usage":{"cpu_time_ns":793179208,"resident_bytes":109133824,"physical_bytes":187728832,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":978,"usage":{"cpu_time_ns":803179541,"resident_bytes":109133824,"physical_bytes":187630528,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1081,"usage":{"cpu_time_ns":913183000,"resident_bytes":109133824,"physical_bytes":187630528,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1191,"usage":{"cpu_time_ns":1024193041,"resident_bytes":109133824,"physical_bytes":187630528,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1298,"usage":{"cpu_time_ns":1124198625,"resident_bytes":109133824,"physical_bytes":187630528,"peak_physical_bytes":187728832,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1408,"usage":{"cpu_time_ns":1240872791,"resident_bytes":126746624,"physical_bytes":201606752,"peak_physical_bytes":201606752,"disk_read_bytes":10391552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1518,"usage":{"cpu_time_ns":1347208333,"resident_bytes":132022272,"physical_bytes":206161528,"peak_physical_bytes":206161528,"disk_read_bytes":10461184,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1628,"usage":{"cpu_time_ns":1364817916,"resident_bytes":132825088,"physical_bytes":268961424,"peak_physical_bytes":268961424,"disk_read_bytes":11153408,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1738,"usage":{"cpu_time_ns":1455558833,"resident_bytes":135774208,"physical_bytes":269043344,"peak_physical_bytes":269043344,"disk_read_bytes":11153408,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1848,"usage":{"cpu_time_ns":1480383583,"resident_bytes":136052736,"physical_bytes":270927504,"peak_physical_bytes":270927504,"disk_read_bytes":11153408,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1959,"usage":{"cpu_time_ns":1493076208,"resident_bytes":138084352,"physical_bytes":367691480,"peak_physical_bytes":367691480,"disk_read_bytes":11755520,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2069,"usage":{"cpu_time_ns":1499107416,"resident_bytes":138592256,"physical_bytes":380831448,"peak_physical_bytes":380831448,"disk_read_bytes":11755520,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2179,"usage":{"cpu_time_ns":1504363291,"resident_bytes":139034624,"physical_bytes":392414936,"peak_physical_bytes":392414936,"disk_read_bytes":11755520,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2289,"usage":{"cpu_time_ns":1510003500,"resident_bytes":139526144,"physical_bytes":405522136,"peak_physical_bytes":405522136,"disk_read_bytes":11755520,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2399,"usage":{"cpu_time_ns":1532340166,"resident_bytes":140886016,"physical_bytes":434751192,"peak_physical_bytes":434751192,"disk_read_bytes":11821056,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2510,"usage":{"cpu_time_ns":1641900958,"resident_bytes":140886016,"physical_bytes":434751192,"peak_physical_bytes":434751192,"disk_read_bytes":11821056,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2620,"usage":{"cpu_time_ns":1727359458,"resident_bytes":160661504,"physical_bytes":285099736,"peak_physical_bytes":447645424,"disk_read_bytes":12017664,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":2730,"usage":{"cpu_time_ns":1785970916,"resident_bytes":161185792,"physical_bytes":318375640,"peak_physical_bytes":447645424,"disk_read_bytes":12017664,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":2831,"usage":{"cpu_time_ns":1886208291,"resident_bytes":161382400,"physical_bytes":318375640,"peak_physical_bytes":447645424,"disk_read_bytes":12021760,"disk_write_bytes":33705984}} +{"event":"test_resource_sample","elapsed_ms":2941,"usage":{"cpu_time_ns":1991190375,"resident_bytes":148144128,"physical_bytes":260867848,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3051,"usage":{"cpu_time_ns":2066984541,"resident_bytes":150044672,"physical_bytes":305776392,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3161,"usage":{"cpu_time_ns":2074594333,"resident_bytes":150274048,"physical_bytes":373950216,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3272,"usage":{"cpu_time_ns":2080296208,"resident_bytes":150765568,"physical_bytes":387057416,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3382,"usage":{"cpu_time_ns":2085985041,"resident_bytes":151257088,"physical_bytes":400164616,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3492,"usage":{"cpu_time_ns":2091268916,"resident_bytes":151699456,"physical_bytes":411748104,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3602,"usage":{"cpu_time_ns":2097277666,"resident_bytes":152190976,"physical_bytes":443942664,"peak_physical_bytes":447645424,"disk_read_bytes":12034048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3708,"usage":{"cpu_time_ns":2535618166,"resident_bytes":195903488,"physical_bytes":388417360,"peak_physical_bytes":484345632,"disk_read_bytes":12247040,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3814,"usage":{"cpu_time_ns":2681934000,"resident_bytes":205062144,"physical_bytes":482248552,"peak_physical_bytes":484345632,"disk_read_bytes":12247040,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":3925,"usage":{"cpu_time_ns":2984151416,"resident_bytes":208437248,"physical_bytes":484689768,"peak_physical_bytes":484689768,"disk_read_bytes":12247040,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4035,"usage":{"cpu_time_ns":3411319916,"resident_bytes":209207296,"physical_bytes":484788072,"peak_physical_bytes":484951912,"disk_read_bytes":12247040,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4145,"usage":{"cpu_time_ns":3521395875,"resident_bytes":209272832,"physical_bytes":484804456,"peak_physical_bytes":484951912,"disk_read_bytes":12247040,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":4248,"usage":{"cpu_time_ns":3615762833,"resident_bytes":203587584,"physical_bytes":366987112,"peak_physical_bytes":497485696,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4358,"usage":{"cpu_time_ns":3701139166,"resident_bytes":205537280,"physical_bytes":431228776,"peak_physical_bytes":497485696,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4464,"usage":{"cpu_time_ns":3708145125,"resident_bytes":207290368,"physical_bytes":493815656,"peak_physical_bytes":497485696,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4574,"usage":{"cpu_time_ns":3714040458,"resident_bytes":207781888,"physical_bytes":512624488,"peak_physical_bytes":512624488,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4685,"usage":{"cpu_time_ns":3719232750,"resident_bytes":208224256,"physical_bytes":524683112,"peak_physical_bytes":524683112,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4787,"usage":{"cpu_time_ns":3724906291,"resident_bytes":208666624,"physical_bytes":536266600,"peak_physical_bytes":536266600,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4896,"usage":{"cpu_time_ns":3731142000,"resident_bytes":209158144,"physical_bytes":549373800,"peak_physical_bytes":549373800,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":4999,"usage":{"cpu_time_ns":3942764875,"resident_bytes":211648512,"physical_bytes":477726616,"peak_physical_bytes":601360232,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5110,"usage":{"cpu_time_ns":4171788250,"resident_bytes":213417984,"physical_bytes":489736088,"peak_physical_bytes":601360232,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5221,"usage":{"cpu_time_ns":4603727416,"resident_bytes":214007808,"physical_bytes":489785240,"peak_physical_bytes":601360232,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5331,"usage":{"cpu_time_ns":5030277958,"resident_bytes":214073344,"physical_bytes":489834392,"peak_physical_bytes":601360232,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5441,"usage":{"cpu_time_ns":5125664208,"resident_bytes":214597632,"physical_bytes":489850776,"peak_physical_bytes":601360232,"disk_read_bytes":12259328,"disk_write_bytes":77606912}} +{"event":"test_resource_sample","elapsed_ms":5551,"usage":{"cpu_time_ns":5227421291,"resident_bytes":229031936,"physical_bytes":489850776,"peak_physical_bytes":601360232,"disk_read_bytes":12279808,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":5661,"usage":{"cpu_time_ns":5311782708,"resident_bytes":230948864,"physical_bytes":554092440,"peak_physical_bytes":601360232,"disk_read_bytes":12279808,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":5764,"usage":{"cpu_time_ns":5317321833,"resident_bytes":229294080,"physical_bytes":504416152,"peak_physical_bytes":601360232,"disk_read_bytes":12279808,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":5874,"usage":{"cpu_time_ns":5322005333,"resident_bytes":229294080,"physical_bytes":504416152,"peak_physical_bytes":601360232,"disk_read_bytes":12279808,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":5975,"usage":{"cpu_time_ns":5410758875,"resident_bytes":229343232,"physical_bytes":504448920,"peak_physical_bytes":601360232,"disk_read_bytes":12279808,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":6085,"usage":{"cpu_time_ns":5802163208,"resident_bytes":229965824,"physical_bytes":492701592,"peak_physical_bytes":601360232,"disk_read_bytes":12279808,"disk_write_bytes":104198144}} +{"event":"test_resource_sample","elapsed_ms":6195,"usage":{"cpu_time_ns":5911762250,"resident_bytes":230850560,"physical_bytes":492701592,"peak_physical_bytes":601360232,"disk_read_bytes":12288000,"disk_write_bytes":120975360}} +{"event":"test_resource_sample","elapsed_ms":6305,"usage":{"cpu_time_ns":6013668791,"resident_bytes":222904320,"physical_bytes":385861480,"peak_physical_bytes":601360232,"disk_read_bytes":38891520,"disk_write_bytes":132325376}} +{"event":"test_resource_sample","elapsed_ms":6415,"usage":{"cpu_time_ns":6090444875,"resident_bytes":224690176,"physical_bytes":449972072,"peak_physical_bytes":601360232,"disk_read_bytes":38891520,"disk_write_bytes":132325376}} +{"event":"test_resource_sample","elapsed_ms":6525,"usage":{"cpu_time_ns":6096262625,"resident_bytes":224706560,"physical_bytes":410306408,"peak_physical_bytes":601360232,"disk_read_bytes":38891520,"disk_write_bytes":132325376}} +{"event":"test_resource_sample","elapsed_ms":6636,"usage":{"cpu_time_ns":6101809250,"resident_bytes":224706560,"physical_bytes":433276776,"peak_physical_bytes":601360232,"disk_read_bytes":38891520,"disk_write_bytes":132325376}} +{"event":"test_resource_sample","elapsed_ms":6746,"usage":{"cpu_time_ns":6446828208,"resident_bytes":228212736,"physical_bytes":448989056,"peak_physical_bytes":601360232,"disk_read_bytes":38891520,"disk_write_bytes":132325376}} +{"event":"test_resource_sample","elapsed_ms":6856,"usage":{"cpu_time_ns":6607495666,"resident_bytes":228737024,"physical_bytes":451725184,"peak_physical_bytes":601360232,"disk_read_bytes":38891520,"disk_write_bytes":132325376}} +{"event":"test_resource_sample","elapsed_ms":6966,"usage":{"cpu_time_ns":6707505833,"resident_bytes":230162432,"physical_bytes":451741568,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7077,"usage":{"cpu_time_ns":6812796416,"resident_bytes":232030208,"physical_bytes":515966848,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7187,"usage":{"cpu_time_ns":6828540666,"resident_bytes":233668608,"physical_bytes":515966848,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7297,"usage":{"cpu_time_ns":6920792875,"resident_bytes":232062976,"physical_bytes":515999616,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7400,"usage":{"cpu_time_ns":6927874916,"resident_bytes":236322816,"physical_bytes":575309696,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7510,"usage":{"cpu_time_ns":6936864125,"resident_bytes":237092864,"physical_bytes":581633920,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7620,"usage":{"cpu_time_ns":6946076750,"resident_bytes":237846528,"physical_bytes":582387584,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7729,"usage":{"cpu_time_ns":6954095666,"resident_bytes":238501888,"physical_bytes":583042944,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7839,"usage":{"cpu_time_ns":6962969291,"resident_bytes":239222784,"physical_bytes":583370624,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":7942,"usage":{"cpu_time_ns":6974284583,"resident_bytes":239845376,"physical_bytes":584304512,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":8052,"usage":{"cpu_time_ns":7409587958,"resident_bytes":239894528,"physical_bytes":461539200,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":8162,"usage":{"cpu_time_ns":7519416625,"resident_bytes":239894528,"physical_bytes":461539200,"peak_physical_bytes":601360232,"disk_read_bytes":38912000,"disk_write_bytes":160452608}} +{"event":"test_resource_sample","elapsed_ms":8264,"usage":{"cpu_time_ns":7616060750,"resident_bytes":252198912,"physical_bytes":461539200,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":8374,"usage":{"cpu_time_ns":7691425791,"resident_bytes":254099456,"physical_bytes":525764480,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":8481,"usage":{"cpu_time_ns":7731651416,"resident_bytes":255737856,"physical_bytes":525764480,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":8591,"usage":{"cpu_time_ns":7799902208,"resident_bytes":254115840,"physical_bytes":525780864,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":8699,"usage":{"cpu_time_ns":7848593166,"resident_bytes":255754240,"physical_bytes":525780864,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":8809,"usage":{"cpu_time_ns":7908017625,"resident_bytes":254099456,"physical_bytes":525764480,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":8919,"usage":{"cpu_time_ns":7914622000,"resident_bytes":254410752,"physical_bytes":480331648,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9024,"usage":{"cpu_time_ns":7918761250,"resident_bytes":254410752,"physical_bytes":480331648,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9131,"usage":{"cpu_time_ns":7928645208,"resident_bytes":254722048,"physical_bytes":582125440,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9241,"usage":{"cpu_time_ns":7937778500,"resident_bytes":255492096,"physical_bytes":582879104,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9351,"usage":{"cpu_time_ns":7945633041,"resident_bytes":256147456,"physical_bytes":583534464,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9461,"usage":{"cpu_time_ns":7955466125,"resident_bytes":256966656,"physical_bytes":584353664,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9564,"usage":{"cpu_time_ns":7963225708,"resident_bytes":257622016,"physical_bytes":585009024,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9665,"usage":{"cpu_time_ns":8066724208,"resident_bytes":257966080,"physical_bytes":467289984,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9775,"usage":{"cpu_time_ns":8474248208,"resident_bytes":257966080,"physical_bytes":460474240,"peak_physical_bytes":601360232,"disk_read_bytes":38928384,"disk_write_bytes":200904704}} +{"event":"test_resource_sample","elapsed_ms":9885,"usage":{"cpu_time_ns":8583266833,"resident_bytes":270729216,"physical_bytes":460474240,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":251236352}} +{"event":"test_resource_sample","elapsed_ms":9995,"usage":{"cpu_time_ns":8688466750,"resident_bytes":223100928,"physical_bytes":358156136,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10098,"usage":{"cpu_time_ns":8711523041,"resident_bytes":222429184,"physical_bytes":411535208,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10201,"usage":{"cpu_time_ns":8721726416,"resident_bytes":222806016,"physical_bytes":485017448,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10311,"usage":{"cpu_time_ns":8727381958,"resident_bytes":223248384,"physical_bytes":496600936,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10414,"usage":{"cpu_time_ns":8732902750,"resident_bytes":223690752,"physical_bytes":508610408,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10515,"usage":{"cpu_time_ns":8737633791,"resident_bytes":224083968,"physical_bytes":519096168,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10626,"usage":{"cpu_time_ns":8743981500,"resident_bytes":224575488,"physical_bytes":551126888,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10736,"usage":{"cpu_time_ns":9179050708,"resident_bytes":228032512,"physical_bytes":500074392,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10846,"usage":{"cpu_time_ns":9607171375,"resident_bytes":228589568,"physical_bytes":504252312,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":10948,"usage":{"cpu_time_ns":9654308083,"resident_bytes":229146624,"physical_bytes":504285080,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":11058,"usage":{"cpu_time_ns":9764272291,"resident_bytes":232669184,"physical_bytes":504285080,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":254836736}} +{"event":"test_resource_sample","elapsed_ms":11164,"usage":{"cpu_time_ns":9865513916,"resident_bytes":223969280,"physical_bytes":395118440,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11274,"usage":{"cpu_time_ns":9900431958,"resident_bytes":224722944,"physical_bytes":449759080,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11381,"usage":{"cpu_time_ns":9980970333,"resident_bytes":226361344,"physical_bytes":449759080,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11491,"usage":{"cpu_time_ns":10008942166,"resident_bytes":224755712,"physical_bytes":449791848,"peak_physical_bytes":601360232,"disk_read_bytes":38948864,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11598,"usage":{"cpu_time_ns":10025156833,"resident_bytes":227459072,"physical_bytes":643467112,"peak_physical_bytes":643467112,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11708,"usage":{"cpu_time_ns":10035450875,"resident_bytes":228474880,"physical_bytes":653100904,"peak_physical_bytes":653100904,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11818,"usage":{"cpu_time_ns":10046117208,"resident_bytes":229523456,"physical_bytes":663193448,"peak_physical_bytes":663193448,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":11928,"usage":{"cpu_time_ns":10056543416,"resident_bytes":230539264,"physical_bytes":672827240,"peak_physical_bytes":672827240,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12034,"usage":{"cpu_time_ns":10067115291,"resident_bytes":231587840,"physical_bytes":682919784,"peak_physical_bytes":682919784,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12144,"usage":{"cpu_time_ns":10077606875,"resident_bytes":232603648,"physical_bytes":692553576,"peak_physical_bytes":692553576,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12254,"usage":{"cpu_time_ns":10201624083,"resident_bytes":234848256,"physical_bytes":505087896,"peak_physical_bytes":747063144,"disk_read_bytes":39014400,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12364,"usage":{"cpu_time_ns":10523251625,"resident_bytes":237207552,"physical_bytes":517080984,"peak_physical_bytes":747063144,"disk_read_bytes":39047168,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12475,"usage":{"cpu_time_ns":10950040875,"resident_bytes":237273088,"physical_bytes":517130136,"peak_physical_bytes":747063144,"disk_read_bytes":39047168,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12581,"usage":{"cpu_time_ns":11380337916,"resident_bytes":237813760,"physical_bytes":519882648,"peak_physical_bytes":747063144,"disk_read_bytes":39047168,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12691,"usage":{"cpu_time_ns":11490253666,"resident_bytes":237813760,"physical_bytes":519882648,"peak_physical_bytes":747063144,"disk_read_bytes":39047168,"disk_write_bytes":281128960}} +{"event":"test_resource_sample","elapsed_ms":12798,"usage":{"cpu_time_ns":11591107666,"resident_bytes":263815168,"physical_bytes":519882648,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":12908,"usage":{"cpu_time_ns":11666141750,"resident_bytes":265715712,"physical_bytes":584107928,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13018,"usage":{"cpu_time_ns":11710186333,"resident_bytes":267354112,"physical_bytes":584107928,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13120,"usage":{"cpu_time_ns":11774231000,"resident_bytes":265748480,"physical_bytes":584140696,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13230,"usage":{"cpu_time_ns":11819233625,"resident_bytes":230899712,"physical_bytes":403802032,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13340,"usage":{"cpu_time_ns":11902769000,"resident_bytes":231768064,"physical_bytes":461064112,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13450,"usage":{"cpu_time_ns":11911332291,"resident_bytes":231948288,"physical_bytes":537020336,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13560,"usage":{"cpu_time_ns":11917327625,"resident_bytes":232390656,"physical_bytes":548603824,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13671,"usage":{"cpu_time_ns":11923259083,"resident_bytes":232833024,"physical_bytes":560613296,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13781,"usage":{"cpu_time_ns":11929796458,"resident_bytes":233226240,"physical_bytes":541575088,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":13891,"usage":{"cpu_time_ns":11935700958,"resident_bytes":233668608,"physical_bytes":541149104,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":14001,"usage":{"cpu_time_ns":11945452541,"resident_bytes":234438656,"physical_bytes":595691440,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":14111,"usage":{"cpu_time_ns":12379474208,"resident_bytes":237355008,"physical_bytes":517818336,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":14221,"usage":{"cpu_time_ns":12807909208,"resident_bytes":238354432,"physical_bytes":517834720,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":14331,"usage":{"cpu_time_ns":12912712875,"resident_bytes":238895104,"physical_bytes":517834720,"peak_physical_bytes":747063144,"disk_read_bytes":39051264,"disk_write_bytes":319275008}} +{"event":"test_resource_sample","elapsed_ms":14441,"usage":{"cpu_time_ns":12979513958,"resident_bytes":232620032,"physical_bytes":496813952,"peak_physical_bytes":747063144,"disk_read_bytes":39075840,"disk_write_bytes":346161152}} +{"event":"test_resource_sample","elapsed_ms":14548,"usage":{"cpu_time_ns":12983783458,"resident_bytes":232620032,"physical_bytes":520570752,"peak_physical_bytes":747063144,"disk_read_bytes":39075840,"disk_write_bytes":346161152}} +{"event":"test_resource_sample","elapsed_ms":14658,"usage":{"cpu_time_ns":12992564791,"resident_bytes":233357312,"physical_bytes":562431872,"peak_physical_bytes":747063144,"disk_read_bytes":39075840,"disk_write_bytes":346161152}} +{"event":"test_resource_sample","elapsed_ms":14768,"usage":{"cpu_time_ns":13433878916,"resident_bytes":236748800,"physical_bytes":578373528,"peak_physical_bytes":747063144,"disk_read_bytes":39075840,"disk_write_bytes":346161152}} +{"event":"test_resource_sample","elapsed_ms":14878,"usage":{"cpu_time_ns":13543829166,"resident_bytes":236748800,"physical_bytes":578373528,"peak_physical_bytes":747063144,"disk_read_bytes":39075840,"disk_write_bytes":346161152}} +{"event":"test_resource_sample","elapsed_ms":14981,"usage":{"cpu_time_ns":13631951291,"resident_bytes":218923008,"physical_bytes":298518352,"peak_physical_bytes":747063144,"disk_read_bytes":39084032,"disk_write_bytes":360722432}} +{"event":"test_resource_sample","elapsed_ms":15091,"usage":{"cpu_time_ns":13750884625,"resident_bytes":217546752,"physical_bytes":295946064,"peak_physical_bytes":747063144,"disk_read_bytes":39084032,"disk_write_bytes":360722432}} +{"event":"test_resource_summary","elapsed_ms":15199,"peak":{"cpu_time_ns":13750884625,"resident_bytes":270729216,"physical_bytes":692553576,"peak_physical_bytes":747063144,"disk_read_bytes":39084032,"disk_write_bytes":360722432},"error":null} diff --git a/docs/issue100-vision/full-resident.log b/docs/issue100-vision/full-resident.log new file mode 100644 index 0000000..cd07545 --- /dev/null +++ b/docs/issue100-vision/full-resident.log @@ -0,0 +1,18 @@ + +running 1 test +test engine::metal::deepseek_vision::tests::issue100_full_model_images_cache_and_dspark ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal model views created in 1.707 ms, residency requested in 460.920 ms, warmup 3.718 ms (mapped 82697.67 MiB from offset 5.09 MiB) +ds4: Metal mapped mmaped model as 2 overlapping shared buffers +ds4: Metal model views created in 0.118 ms, residency requested in 771.569 ms, warmup 0.756 ms (mapped 5711.67 MiB from offset 0.01 MiB) +ds4: Metal mapped mmaped model as 3 overlapping shared buffers +red: The image features a solid red square centered on a white background. +DSpark drafted=15 accepted=10 +blue: A solid blue circle centered on a white background. +pair: The first shape is red, and the second shape is blue. +tool image: The color in the image is blue. +issue100: production rendering, image recognition, multi-image/tool input, same-layout image invalidation, disk/warm KV and DSpark target equivalence passed +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 15.10s diff --git a/docs/issue100-vision/full-ssd-resources.jsonl b/docs/issue100-vision/full-ssd-resources.jsonl new file mode 100644 index 0000000..1ab4ee2 --- /dev/null +++ b/docs/issue100-vision/full-ssd-resources.jsonl @@ -0,0 +1,293 @@ +{"event":"test_supervisor_started","pid":37684,"limit_bytes":120259084288,"start_seconds":60.0,"idle_seconds":120.0,"sample_ms":100} +{"event":"test_resource_sample","elapsed_ms":0,"usage":{"cpu_time_ns":172458,"resident_bytes":671744,"physical_bytes":196680,"peak_physical_bytes":213064,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":110,"usage":{"cpu_time_ns":79014291,"resident_bytes":60178432,"physical_bytes":38978136,"peak_physical_bytes":38978136,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":215,"usage":{"cpu_time_ns":165433500,"resident_bytes":129089536,"physical_bytes":83756592,"peak_physical_bytes":83756592,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":325,"usage":{"cpu_time_ns":261924375,"resident_bytes":134348800,"physical_bytes":269092472,"peak_physical_bytes":269092472,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":436,"usage":{"cpu_time_ns":275562625,"resident_bytes":137035776,"physical_bytes":269158008,"peak_physical_bytes":269158008,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":546,"usage":{"cpu_time_ns":381450875,"resident_bytes":137330688,"physical_bytes":271042144,"peak_physical_bytes":335267448,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":656,"usage":{"cpu_time_ns":392603041,"resident_bytes":146014208,"physical_bytes":286328488,"peak_physical_bytes":335267448,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":756,"usage":{"cpu_time_ns":626728583,"resident_bytes":1249771520,"physical_bytes":3413428696,"peak_physical_bytes":3413281240,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":865,"usage":{"cpu_time_ns":847348666,"resident_bytes":2936602624,"physical_bytes":4638740080,"peak_physical_bytes":4638740080,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":976,"usage":{"cpu_time_ns":1178879833,"resident_bytes":4585914368,"physical_bytes":6545969976,"peak_physical_bytes":6545658680,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1083,"usage":{"cpu_time_ns":1349362625,"resident_bytes":5337268224,"physical_bytes":8941491528,"peak_physical_bytes":8941491528,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1193,"usage":{"cpu_time_ns":1641163083,"resident_bytes":7489110016,"physical_bytes":8944147272,"peak_physical_bytes":8944147272,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1304,"usage":{"cpu_time_ns":1884554166,"resident_bytes":9855336448,"physical_bytes":10076299784,"peak_physical_bytes":10076021256,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1414,"usage":{"cpu_time_ns":2087453458,"resident_bytes":10206986240,"physical_bytes":13239427856,"peak_physical_bytes":13239427856,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1524,"usage":{"cpu_time_ns":2372938375,"resident_bytes":12270059520,"physical_bytes":13244098760,"peak_physical_bytes":13244098760,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1632,"usage":{"cpu_time_ns":2593712208,"resident_bytes":13402570752,"physical_bytes":17385925648,"peak_physical_bytes":17385581584,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1742,"usage":{"cpu_time_ns":2827094500,"resident_bytes":15023489024,"physical_bytes":17537298576,"peak_physical_bytes":17537298576,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1849,"usage":{"cpu_time_ns":3100383125,"resident_bytes":17033723904,"physical_bytes":17541035568,"peak_physical_bytes":17541035568,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1959,"usage":{"cpu_time_ns":3267652333,"resident_bytes":17762811904,"physical_bytes":21469231192,"peak_physical_bytes":21469231192,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2066,"usage":{"cpu_time_ns":3300018583,"resident_bytes":17762811904,"physical_bytes":21831710808,"peak_physical_bytes":21831710808,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2166,"usage":{"cpu_time_ns":3379156041,"resident_bytes":18569756672,"physical_bytes":21833661080,"peak_physical_bytes":21833661080,"disk_read_bytes":151552,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2272,"usage":{"cpu_time_ns":3662131166,"resident_bytes":20141162496,"physical_bytes":21837053696,"peak_physical_bytes":21837086464,"disk_read_bytes":495616,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2382,"usage":{"cpu_time_ns":3852920083,"resident_bytes":21920727040,"physical_bytes":22236775488,"peak_physical_bytes":22236775488,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2484,"usage":{"cpu_time_ns":3967280583,"resident_bytes":22012739584,"physical_bytes":23021487216,"peak_physical_bytes":23021323376,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2584,"usage":{"cpu_time_ns":3983733041,"resident_bytes":22012788736,"physical_bytes":23523312752,"peak_physical_bytes":23523312752,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2694,"usage":{"cpu_time_ns":4016687291,"resident_bytes":22011412480,"physical_bytes":24661623920,"peak_physical_bytes":24661443696,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2800,"usage":{"cpu_time_ns":4041146625,"resident_bytes":22011412480,"physical_bytes":25399985264,"peak_physical_bytes":25399985264,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2901,"usage":{"cpu_time_ns":4055634333,"resident_bytes":22010118144,"physical_bytes":25707185264,"peak_physical_bytes":25707185264,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3011,"usage":{"cpu_time_ns":4107557750,"resident_bytes":22010118144,"physical_bytes":26129106032,"peak_physical_bytes":26129106032,"disk_read_bytes":675840,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3115,"usage":{"cpu_time_ns":4365355458,"resident_bytes":24474632192,"physical_bytes":26132974432,"peak_physical_bytes":26132974432,"disk_read_bytes":1003520,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3225,"usage":{"cpu_time_ns":4619785166,"resident_bytes":25976422400,"physical_bytes":26304810952,"peak_physical_bytes":26304810952,"disk_read_bytes":1167360,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3331,"usage":{"cpu_time_ns":4690986541,"resident_bytes":26662043648,"physical_bytes":26988106176,"peak_physical_bytes":26988106176,"disk_read_bytes":1531904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3432,"usage":{"cpu_time_ns":4811608291,"resident_bytes":26904952832,"physical_bytes":27541475944,"peak_physical_bytes":27541475944,"disk_read_bytes":1613824,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3533,"usage":{"cpu_time_ns":4841416375,"resident_bytes":26904035328,"physical_bytes":28270252648,"peak_physical_bytes":28270252648,"disk_read_bytes":1613824,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3633,"usage":{"cpu_time_ns":4863742666,"resident_bytes":26901102592,"physical_bytes":29019198056,"peak_physical_bytes":29019198056,"disk_read_bytes":1613824,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3736,"usage":{"cpu_time_ns":4903229916,"resident_bytes":26901037056,"physical_bytes":29393785448,"peak_physical_bytes":29393785448,"disk_read_bytes":1613824,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3841,"usage":{"cpu_time_ns":5122084916,"resident_bytes":29138714624,"physical_bytes":29483505864,"peak_physical_bytes":29483194568,"disk_read_bytes":2662400,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3951,"usage":{"cpu_time_ns":5374999500,"resident_bytes":29573578752,"physical_bytes":29910047256,"peak_physical_bytes":29910047256,"disk_read_bytes":3551232,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4061,"usage":{"cpu_time_ns":5577649916,"resident_bytes":29601906688,"physical_bytes":29939472944,"peak_physical_bytes":29939505712,"disk_read_bytes":4763648,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4166,"usage":{"cpu_time_ns":5777629208,"resident_bytes":29649616896,"physical_bytes":29991836256,"peak_physical_bytes":29991869024,"disk_read_bytes":26390528,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4277,"usage":{"cpu_time_ns":6085482000,"resident_bytes":29727326208,"physical_bytes":30072363664,"peak_physical_bytes":30072363664,"disk_read_bytes":65073152,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4382,"usage":{"cpu_time_ns":6389288875,"resident_bytes":29802315776,"physical_bytes":30147336896,"peak_physical_bytes":30147353280,"disk_read_bytes":154578944,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4492,"usage":{"cpu_time_ns":6722753375,"resident_bytes":29852008448,"physical_bytes":30199536344,"peak_physical_bytes":30199536344,"disk_read_bytes":184774656,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4599,"usage":{"cpu_time_ns":7061638375,"resident_bytes":29880516608,"physical_bytes":30231599856,"peak_physical_bytes":30231616240,"disk_read_bytes":230371328,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4702,"usage":{"cpu_time_ns":7353204083,"resident_bytes":29944348672,"physical_bytes":30305032992,"peak_physical_bytes":30305032992,"disk_read_bytes":279506944,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4807,"usage":{"cpu_time_ns":7656767625,"resident_bytes":30010556416,"physical_bytes":30384970576,"peak_physical_bytes":30384970576,"disk_read_bytes":332034048,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":4918,"usage":{"cpu_time_ns":7841943791,"resident_bytes":30041243648,"physical_bytes":30414019432,"peak_physical_bytes":30414019432,"disk_read_bytes":333590528,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":5028,"usage":{"cpu_time_ns":7945233875,"resident_bytes":30048305152,"physical_bytes":30414297960,"peak_physical_bytes":30414297960,"disk_read_bytes":333590528,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":5138,"usage":{"cpu_time_ns":8037639833,"resident_bytes":30076436480,"physical_bytes":30277819264,"peak_physical_bytes":30426897280,"disk_read_bytes":345223168,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":5248,"usage":{"cpu_time_ns":8166172708,"resident_bytes":30076485632,"physical_bytes":30311603072,"peak_physical_bytes":30426897280,"disk_read_bytes":362643456,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":5358,"usage":{"cpu_time_ns":8326059375,"resident_bytes":30077042688,"physical_bytes":30312242048,"peak_physical_bytes":30426897280,"disk_read_bytes":365887488,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":5465,"usage":{"cpu_time_ns":8452181750,"resident_bytes":30077091840,"physical_bytes":30312258432,"peak_physical_bytes":30426897280,"disk_read_bytes":366034944,"disk_write_bytes":25317376}} +{"event":"test_resource_sample","elapsed_ms":5575,"usage":{"cpu_time_ns":8561194500,"resident_bytes":30077419520,"physical_bytes":30312258432,"peak_physical_bytes":30426897280,"disk_read_bytes":366034944,"disk_write_bytes":33705984}} +{"event":"test_resource_sample","elapsed_ms":5686,"usage":{"cpu_time_ns":8666788041,"resident_bytes":30070013952,"physical_bytes":30267005848,"peak_physical_bytes":30426897280,"disk_read_bytes":367099904,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":5796,"usage":{"cpu_time_ns":8775923125,"resident_bytes":30085627904,"physical_bytes":30281505688,"peak_physical_bytes":30426897280,"disk_read_bytes":392626176,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":5906,"usage":{"cpu_time_ns":8814061958,"resident_bytes":30083891200,"physical_bytes":30317370288,"peak_physical_bytes":30426897280,"disk_read_bytes":1296498688,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6016,"usage":{"cpu_time_ns":8819137750,"resident_bytes":30083891200,"physical_bytes":30317370288,"peak_physical_bytes":30426897280,"disk_read_bytes":1325469696,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6126,"usage":{"cpu_time_ns":8991600500,"resident_bytes":30074470400,"physical_bytes":30360017840,"peak_physical_bytes":30426897280,"disk_read_bytes":1337217024,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6237,"usage":{"cpu_time_ns":9259636125,"resident_bytes":30074667008,"physical_bytes":30365260720,"peak_physical_bytes":30426897280,"disk_read_bytes":1339346944,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6347,"usage":{"cpu_time_ns":9522133791,"resident_bytes":30074863616,"physical_bytes":30370503600,"peak_physical_bytes":30426897280,"disk_read_bytes":1393807360,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6455,"usage":{"cpu_time_ns":9758080750,"resident_bytes":30075076608,"physical_bytes":30375746480,"peak_physical_bytes":30426897280,"disk_read_bytes":1394642944,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6565,"usage":{"cpu_time_ns":10011188666,"resident_bytes":30075273216,"physical_bytes":30380858288,"peak_physical_bytes":30426897280,"disk_read_bytes":1394642944,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6675,"usage":{"cpu_time_ns":10307665458,"resident_bytes":30075420672,"physical_bytes":30384954288,"peak_physical_bytes":30426897280,"disk_read_bytes":1394642944,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6782,"usage":{"cpu_time_ns":10580451791,"resident_bytes":30075568128,"physical_bytes":30388673456,"peak_physical_bytes":30426897280,"disk_read_bytes":1394823168,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":6892,"usage":{"cpu_time_ns":10896423833,"resident_bytes":30075715584,"physical_bytes":30392818608,"peak_physical_bytes":30426897280,"disk_read_bytes":1395314688,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7001,"usage":{"cpu_time_ns":11164930083,"resident_bytes":30075912192,"physical_bytes":30398061488,"peak_physical_bytes":30426897280,"disk_read_bytes":1395970048,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7111,"usage":{"cpu_time_ns":11499992666,"resident_bytes":30076059648,"physical_bytes":30401797040,"peak_physical_bytes":30426897280,"disk_read_bytes":1396494336,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7221,"usage":{"cpu_time_ns":11790385083,"resident_bytes":30076207104,"physical_bytes":30405942192,"peak_physical_bytes":30426897280,"disk_read_bytes":1396969472,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7331,"usage":{"cpu_time_ns":12101886958,"resident_bytes":30076354560,"physical_bytes":30428781488,"peak_physical_bytes":30428814256,"disk_read_bytes":1397493760,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7442,"usage":{"cpu_time_ns":12238553375,"resident_bytes":30076911616,"physical_bytes":30433549208,"peak_physical_bytes":30433549208,"disk_read_bytes":1405079552,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7543,"usage":{"cpu_time_ns":12275020583,"resident_bytes":30076878848,"physical_bytes":30434401176,"peak_physical_bytes":30434597784,"disk_read_bytes":2168623104,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7649,"usage":{"cpu_time_ns":12303557666,"resident_bytes":30070734848,"physical_bytes":30434401176,"peak_physical_bytes":30434597784,"disk_read_bytes":2840956928,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7759,"usage":{"cpu_time_ns":12377144416,"resident_bytes":30063755264,"physical_bytes":30344436632,"peak_physical_bytes":30456912792,"disk_read_bytes":3501359104,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7869,"usage":{"cpu_time_ns":12895256958,"resident_bytes":30102257664,"physical_bytes":30347598792,"peak_physical_bytes":30456912792,"disk_read_bytes":3513008128,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":7979,"usage":{"cpu_time_ns":13404548375,"resident_bytes":30109450240,"physical_bytes":30354725832,"peak_physical_bytes":30456912792,"disk_read_bytes":3524509696,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":8089,"usage":{"cpu_time_ns":13917976666,"resident_bytes":30109499392,"physical_bytes":30354742216,"peak_physical_bytes":30456912792,"disk_read_bytes":3531341824,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":8200,"usage":{"cpu_time_ns":14435387125,"resident_bytes":30109532160,"physical_bytes":30354742216,"peak_physical_bytes":30456912792,"disk_read_bytes":3542417408,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":8310,"usage":{"cpu_time_ns":14681285291,"resident_bytes":30109548544,"physical_bytes":30354742216,"peak_physical_bytes":30456912792,"disk_read_bytes":3549560832,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":8420,"usage":{"cpu_time_ns":15303234375,"resident_bytes":30109564928,"physical_bytes":30354742216,"peak_physical_bytes":30456912792,"disk_read_bytes":3557015552,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":8531,"usage":{"cpu_time_ns":15556167666,"resident_bytes":30109614080,"physical_bytes":30354709448,"peak_physical_bytes":30456912792,"disk_read_bytes":3557015552,"disk_write_bytes":51015680}} +{"event":"test_resource_sample","elapsed_ms":8634,"usage":{"cpu_time_ns":15651460416,"resident_bytes":30107254784,"physical_bytes":30257798112,"peak_physical_bytes":30456912792,"disk_read_bytes":3557072896,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":8744,"usage":{"cpu_time_ns":15760740875,"resident_bytes":30123098112,"physical_bytes":30273625032,"peak_physical_bytes":30456912792,"disk_read_bytes":3585167360,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":8849,"usage":{"cpu_time_ns":15826246500,"resident_bytes":30122262528,"physical_bytes":30328757216,"peak_physical_bytes":30456912792,"disk_read_bytes":3585167360,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":8959,"usage":{"cpu_time_ns":15924891833,"resident_bytes":30106697728,"physical_bytes":30346124280,"peak_physical_bytes":30456912792,"disk_read_bytes":3587559424,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9061,"usage":{"cpu_time_ns":16231248291,"resident_bytes":30106845184,"physical_bytes":30369520632,"peak_physical_bytes":30456912792,"disk_read_bytes":3642888192,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9163,"usage":{"cpu_time_ns":16508232875,"resident_bytes":30107090944,"physical_bytes":30374812664,"peak_physical_bytes":30456912792,"disk_read_bytes":3643904000,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9274,"usage":{"cpu_time_ns":16835094875,"resident_bytes":30107238400,"physical_bytes":30379006968,"peak_physical_bytes":30456912792,"disk_read_bytes":3644755968,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9384,"usage":{"cpu_time_ns":17064060750,"resident_bytes":30107435008,"physical_bytes":30384249848,"peak_physical_bytes":30456912792,"disk_read_bytes":3672969216,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9494,"usage":{"cpu_time_ns":17367804333,"resident_bytes":30107582464,"physical_bytes":30387969016,"peak_physical_bytes":30456912792,"disk_read_bytes":3673378816,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9599,"usage":{"cpu_time_ns":17635300833,"resident_bytes":30107779072,"physical_bytes":30393211896,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9701,"usage":{"cpu_time_ns":17876276583,"resident_bytes":30107877376,"physical_bytes":30395603960,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9804,"usage":{"cpu_time_ns":18099795833,"resident_bytes":30108073984,"physical_bytes":30400846840,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":9915,"usage":{"cpu_time_ns":18370225458,"resident_bytes":30108221440,"physical_bytes":30404991992,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10025,"usage":{"cpu_time_ns":18649135000,"resident_bytes":30108418048,"physical_bytes":30410234872,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10135,"usage":{"cpu_time_ns":18950571583,"resident_bytes":30108565504,"physical_bytes":30413954040,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10238,"usage":{"cpu_time_ns":19243127416,"resident_bytes":30108712960,"physical_bytes":30418099192,"peak_physical_bytes":30456912792,"disk_read_bytes":3673493504,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10348,"usage":{"cpu_time_ns":19468346291,"resident_bytes":30109204480,"physical_bytes":30440954848,"peak_physical_bytes":30456912792,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10458,"usage":{"cpu_time_ns":19655778208,"resident_bytes":30110433280,"physical_bytes":30357183456,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10568,"usage":{"cpu_time_ns":20202366125,"resident_bytes":30111973376,"physical_bytes":30356986848,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10673,"usage":{"cpu_time_ns":20799819541,"resident_bytes":30111973376,"physical_bytes":30356986848,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10783,"usage":{"cpu_time_ns":21514310958,"resident_bytes":30111973376,"physical_bytes":30356986848,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10884,"usage":{"cpu_time_ns":21680742833,"resident_bytes":30111989760,"physical_bytes":30357150688,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":10994,"usage":{"cpu_time_ns":22207464041,"resident_bytes":30112006144,"physical_bytes":30357167072,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":11104,"usage":{"cpu_time_ns":22741278583,"resident_bytes":30112006144,"physical_bytes":30357167072,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":11215,"usage":{"cpu_time_ns":22851533541,"resident_bytes":30112006144,"physical_bytes":30357167072,"peak_physical_bytes":30465694688,"disk_read_bytes":3674755072,"disk_write_bytes":77099008}} +{"event":"test_resource_sample","elapsed_ms":11325,"usage":{"cpu_time_ns":22954984250,"resident_bytes":30126407680,"physical_bytes":30357167072,"peak_physical_bytes":30465694688,"disk_read_bytes":3674759168,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":11432,"usage":{"cpu_time_ns":23052234291,"resident_bytes":30128291840,"physical_bytes":30421556192,"peak_physical_bytes":30465694688,"disk_read_bytes":3674759168,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":11542,"usage":{"cpu_time_ns":23071331375,"resident_bytes":30130733056,"physical_bytes":30421556192,"peak_physical_bytes":30465694688,"disk_read_bytes":3693780992,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":11646,"usage":{"cpu_time_ns":23209985166,"resident_bytes":30127259648,"physical_bytes":30357363680,"peak_physical_bytes":30465694688,"disk_read_bytes":3727990784,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":11748,"usage":{"cpu_time_ns":23297664208,"resident_bytes":30127259648,"physical_bytes":30357363680,"peak_physical_bytes":30465694688,"disk_read_bytes":3753467904,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":11859,"usage":{"cpu_time_ns":23372176750,"resident_bytes":30134337536,"physical_bytes":30364441568,"peak_physical_bytes":30465694688,"disk_read_bytes":3755483136,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":11965,"usage":{"cpu_time_ns":23459469083,"resident_bytes":30134337536,"physical_bytes":30364441568,"peak_physical_bytes":30465694688,"disk_read_bytes":3766525952,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":12075,"usage":{"cpu_time_ns":23546605333,"resident_bytes":30134337536,"physical_bytes":30364441568,"peak_physical_bytes":30465694688,"disk_read_bytes":3793051648,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":12182,"usage":{"cpu_time_ns":23583082666,"resident_bytes":30141415424,"physical_bytes":30371519456,"peak_physical_bytes":30465694688,"disk_read_bytes":3798605824,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":12289,"usage":{"cpu_time_ns":23633072125,"resident_bytes":30141415424,"physical_bytes":30371519456,"peak_physical_bytes":30465694688,"disk_read_bytes":3836813312,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":12396,"usage":{"cpu_time_ns":24173243166,"resident_bytes":30141480960,"physical_bytes":30371519456,"peak_physical_bytes":30465694688,"disk_read_bytes":3836862464,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":12507,"usage":{"cpu_time_ns":24283430333,"resident_bytes":30141480960,"physical_bytes":30371519456,"peak_physical_bytes":30465694688,"disk_read_bytes":3836862464,"disk_write_bytes":129265664}} +{"event":"test_resource_sample","elapsed_ms":12615,"usage":{"cpu_time_ns":24386579583,"resident_bytes":30124589056,"physical_bytes":30272494560,"peak_physical_bytes":30465694688,"disk_read_bytes":3836866560,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":12716,"usage":{"cpu_time_ns":24487581875,"resident_bytes":30136434688,"physical_bytes":30279801824,"peak_physical_bytes":30465694688,"disk_read_bytes":3836866560,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":12827,"usage":{"cpu_time_ns":24542420333,"resident_bytes":30138351616,"physical_bytes":30343994336,"peak_physical_bytes":30465694688,"disk_read_bytes":3836866560,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":12937,"usage":{"cpu_time_ns":24609636250,"resident_bytes":30138368000,"physical_bytes":30316485600,"peak_physical_bytes":30465694688,"disk_read_bytes":3836866560,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13047,"usage":{"cpu_time_ns":24660025500,"resident_bytes":30138892288,"physical_bytes":30322711520,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13157,"usage":{"cpu_time_ns":24704671666,"resident_bytes":30138892288,"physical_bytes":30322711520,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13267,"usage":{"cpu_time_ns":24729231875,"resident_bytes":30138892288,"physical_bytes":30322711520,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13377,"usage":{"cpu_time_ns":24752070791,"resident_bytes":30138892288,"physical_bytes":30322711520,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13488,"usage":{"cpu_time_ns":24765555666,"resident_bytes":30138892288,"physical_bytes":30322711520,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13598,"usage":{"cpu_time_ns":25018497416,"resident_bytes":30140628992,"physical_bytes":30333918176,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13699,"usage":{"cpu_time_ns":25368037541,"resident_bytes":30141661184,"physical_bytes":30333918176,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":156889088}} +{"event":"test_resource_sample","elapsed_ms":13809,"usage":{"cpu_time_ns":25477831208,"resident_bytes":30142873600,"physical_bytes":30333918176,"peak_physical_bytes":30465694688,"disk_read_bytes":3836882944,"disk_write_bytes":173666304}} +{"event":"test_resource_sample","elapsed_ms":13920,"usage":{"cpu_time_ns":25581216666,"resident_bytes":30143119360,"physical_bytes":30333918176,"peak_physical_bytes":30465694688,"disk_read_bytes":3836956672,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14030,"usage":{"cpu_time_ns":25614751625,"resident_bytes":30145019904,"physical_bytes":30398143456,"peak_physical_bytes":30465694688,"disk_read_bytes":3836956672,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14140,"usage":{"cpu_time_ns":25695362083,"resident_bytes":30146658304,"physical_bytes":30398143456,"peak_physical_bytes":30465694688,"disk_read_bytes":3836956672,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14249,"usage":{"cpu_time_ns":25732065166,"resident_bytes":30145069056,"physical_bytes":30398192608,"peak_physical_bytes":30465694688,"disk_read_bytes":3836956672,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14359,"usage":{"cpu_time_ns":25959792416,"resident_bytes":30146412544,"physical_bytes":30448032736,"peak_physical_bytes":30465694688,"disk_read_bytes":3913797632,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14465,"usage":{"cpu_time_ns":26150650916,"resident_bytes":30146838528,"physical_bytes":30448409568,"peak_physical_bytes":30465694688,"disk_read_bytes":4004794368,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14572,"usage":{"cpu_time_ns":26389519916,"resident_bytes":30147198976,"physical_bytes":30448770016,"peak_physical_bytes":30465694688,"disk_read_bytes":4051668992,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14682,"usage":{"cpu_time_ns":26595790083,"resident_bytes":30148558848,"physical_bytes":30450113504,"peak_physical_bytes":30465694688,"disk_read_bytes":4068954112,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14792,"usage":{"cpu_time_ns":26764758875,"resident_bytes":30148935680,"physical_bytes":30450392032,"peak_physical_bytes":30465694688,"disk_read_bytes":4070150144,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":14902,"usage":{"cpu_time_ns":27008912125,"resident_bytes":30149296128,"physical_bytes":30450752480,"peak_physical_bytes":30465694688,"disk_read_bytes":4071575552,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15013,"usage":{"cpu_time_ns":27269163333,"resident_bytes":30149591040,"physical_bytes":30451047392,"peak_physical_bytes":30465694688,"disk_read_bytes":4072296448,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15123,"usage":{"cpu_time_ns":27524712833,"resident_bytes":30149951488,"physical_bytes":30451407840,"peak_physical_bytes":30465694688,"disk_read_bytes":4083585024,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15233,"usage":{"cpu_time_ns":27783522833,"resident_bytes":30150311936,"physical_bytes":30451768288,"peak_physical_bytes":30465694688,"disk_read_bytes":4092989440,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15343,"usage":{"cpu_time_ns":28006672083,"resident_bytes":30150672384,"physical_bytes":30452128736,"peak_physical_bytes":30465694688,"disk_read_bytes":4104966144,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15453,"usage":{"cpu_time_ns":28262583291,"resident_bytes":30150934528,"physical_bytes":30452390880,"peak_physical_bytes":30465694688,"disk_read_bytes":4110848000,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15563,"usage":{"cpu_time_ns":28436411791,"resident_bytes":30151081984,"physical_bytes":30471363552,"peak_physical_bytes":30471478240,"disk_read_bytes":4230991872,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15673,"usage":{"cpu_time_ns":28522152083,"resident_bytes":30151081984,"physical_bytes":30490237920,"peak_physical_bytes":30490237920,"disk_read_bytes":4239347712,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15782,"usage":{"cpu_time_ns":29074400041,"resident_bytes":30151131136,"physical_bytes":30379105248,"peak_physical_bytes":30490237920,"disk_read_bytes":4239364096,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15885,"usage":{"cpu_time_ns":29177243833,"resident_bytes":30151131136,"physical_bytes":30379105248,"peak_physical_bytes":30490237920,"disk_read_bytes":4239364096,"disk_write_bytes":184512512}} +{"event":"test_resource_sample","elapsed_ms":15996,"usage":{"cpu_time_ns":29282412833,"resident_bytes":30163435520,"physical_bytes":30379105248,"peak_physical_bytes":30490237920,"disk_read_bytes":4239372288,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16106,"usage":{"cpu_time_ns":29379956166,"resident_bytes":30165303296,"physical_bytes":30443330528,"peak_physical_bytes":30490237920,"disk_read_bytes":4239372288,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16216,"usage":{"cpu_time_ns":29401162041,"resident_bytes":30166941696,"physical_bytes":30443330528,"peak_physical_bytes":30490237920,"disk_read_bytes":4239372288,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16326,"usage":{"cpu_time_ns":29502622625,"resident_bytes":30165336064,"physical_bytes":30443363296,"peak_physical_bytes":30490237920,"disk_read_bytes":4239372288,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16436,"usage":{"cpu_time_ns":29519998500,"resident_bytes":30163484672,"physical_bytes":30379154400,"peak_physical_bytes":30490237920,"disk_read_bytes":4239372288,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16547,"usage":{"cpu_time_ns":29620050500,"resident_bytes":30165368832,"physical_bytes":30443396064,"peak_physical_bytes":30490237920,"disk_read_bytes":4239372288,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16657,"usage":{"cpu_time_ns":29683406750,"resident_bytes":30166482944,"physical_bytes":30395390944,"peak_physical_bytes":30490237920,"disk_read_bytes":4250497024,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16765,"usage":{"cpu_time_ns":29947350000,"resident_bytes":30166548480,"physical_bytes":30396210144,"peak_physical_bytes":30490237920,"disk_read_bytes":4309020672,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16875,"usage":{"cpu_time_ns":30196229333,"resident_bytes":30166581248,"physical_bytes":30396210144,"peak_physical_bytes":30490237920,"disk_read_bytes":4312068096,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":16982,"usage":{"cpu_time_ns":30371828625,"resident_bytes":30166581248,"physical_bytes":30396210144,"peak_physical_bytes":30490237920,"disk_read_bytes":4314918912,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17092,"usage":{"cpu_time_ns":30628974000,"resident_bytes":30166482944,"physical_bytes":30396095456,"peak_physical_bytes":30490237920,"disk_read_bytes":4372295680,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17197,"usage":{"cpu_time_ns":30840286958,"resident_bytes":30166908928,"physical_bytes":30487714808,"peak_physical_bytes":30490237920,"disk_read_bytes":4445351936,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17307,"usage":{"cpu_time_ns":31024411875,"resident_bytes":30167236608,"physical_bytes":30488042488,"peak_physical_bytes":30490237920,"disk_read_bytes":4465127424,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17418,"usage":{"cpu_time_ns":31204110208,"resident_bytes":30167662592,"physical_bytes":30488468472,"peak_physical_bytes":30490237920,"disk_read_bytes":4465307648,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17528,"usage":{"cpu_time_ns":31387529458,"resident_bytes":30167990272,"physical_bytes":30488714232,"peak_physical_bytes":30490237920,"disk_read_bytes":4471746560,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17638,"usage":{"cpu_time_ns":31573751916,"resident_bytes":30168317952,"physical_bytes":30489041912,"peak_physical_bytes":30490237920,"disk_read_bytes":4472057856,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17748,"usage":{"cpu_time_ns":31762003708,"resident_bytes":30168727552,"physical_bytes":30489451512,"peak_physical_bytes":30490237920,"disk_read_bytes":4472123392,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17858,"usage":{"cpu_time_ns":31929757500,"resident_bytes":30169055232,"physical_bytes":30489779192,"peak_physical_bytes":30490237920,"disk_read_bytes":4475121664,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":17969,"usage":{"cpu_time_ns":32113021125,"resident_bytes":30169481216,"physical_bytes":30490205176,"peak_physical_bytes":30490237920,"disk_read_bytes":4480774144,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":18079,"usage":{"cpu_time_ns":32287936958,"resident_bytes":30169808896,"physical_bytes":30490532856,"peak_physical_bytes":30490532856,"disk_read_bytes":4483657728,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":18189,"usage":{"cpu_time_ns":32439398458,"resident_bytes":30170005504,"physical_bytes":30490713056,"peak_physical_bytes":30490827768,"disk_read_bytes":4530761728,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":18299,"usage":{"cpu_time_ns":32522143625,"resident_bytes":30170005504,"physical_bytes":30490860512,"peak_physical_bytes":30490860512,"disk_read_bytes":4533006336,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":18409,"usage":{"cpu_time_ns":33100925041,"resident_bytes":30170103808,"physical_bytes":30384823264,"peak_physical_bytes":30490860512,"disk_read_bytes":4533596160,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":18519,"usage":{"cpu_time_ns":33210887083,"resident_bytes":30170103808,"physical_bytes":30384823264,"peak_physical_bytes":30490860512,"disk_read_bytes":4533596160,"disk_write_bytes":224579584}} +{"event":"test_resource_sample","elapsed_ms":18620,"usage":{"cpu_time_ns":33306021083,"resident_bytes":30135828480,"physical_bytes":30284192736,"peak_physical_bytes":30490860512,"disk_read_bytes":4533669888,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":18730,"usage":{"cpu_time_ns":33414152833,"resident_bytes":30147657728,"physical_bytes":30289370080,"peak_physical_bytes":30490860512,"disk_read_bytes":4559912960,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":18832,"usage":{"cpu_time_ns":33488920500,"resident_bytes":30149541888,"physical_bytes":30353611744,"peak_physical_bytes":30490860512,"disk_read_bytes":4559912960,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":18942,"usage":{"cpu_time_ns":33569982125,"resident_bytes":30135369728,"physical_bytes":30372338680,"peak_physical_bytes":30490860512,"disk_read_bytes":4570775552,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19049,"usage":{"cpu_time_ns":33810365125,"resident_bytes":30135566336,"physical_bytes":30397242336,"peak_physical_bytes":30490860512,"disk_read_bytes":4573577216,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19159,"usage":{"cpu_time_ns":33994178166,"resident_bytes":30135762944,"physical_bytes":30402485216,"peak_physical_bytes":30490860512,"disk_read_bytes":4578951168,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19263,"usage":{"cpu_time_ns":34182716625,"resident_bytes":30135959552,"physical_bytes":30407728096,"peak_physical_bytes":30490860512,"disk_read_bytes":4579131392,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19365,"usage":{"cpu_time_ns":34420288500,"resident_bytes":30138220544,"physical_bytes":30415035360,"peak_physical_bytes":30490860512,"disk_read_bytes":4582146048,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19467,"usage":{"cpu_time_ns":34705557958,"resident_bytes":30138368000,"physical_bytes":30417705952,"peak_physical_bytes":30490860512,"disk_read_bytes":4582342656,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19577,"usage":{"cpu_time_ns":34939255625,"resident_bytes":30138466304,"physical_bytes":30420720608,"peak_physical_bytes":30490860512,"disk_read_bytes":4582375424,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19688,"usage":{"cpu_time_ns":35300182250,"resident_bytes":30138613760,"physical_bytes":30424865760,"peak_physical_bytes":30490860512,"disk_read_bytes":4582424576,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19798,"usage":{"cpu_time_ns":35573245375,"resident_bytes":30138761216,"physical_bytes":30428584928,"peak_physical_bytes":30490860512,"disk_read_bytes":4582490112,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":19908,"usage":{"cpu_time_ns":35878756583,"resident_bytes":30138957824,"physical_bytes":30433827808,"peak_physical_bytes":30490860512,"disk_read_bytes":4585406464,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20018,"usage":{"cpu_time_ns":36185725041,"resident_bytes":30138646528,"physical_bytes":30437972960,"peak_physical_bytes":30490860512,"disk_read_bytes":4585996288,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20128,"usage":{"cpu_time_ns":36479011166,"resident_bytes":30138793984,"physical_bytes":30441692128,"peak_physical_bytes":30490860512,"disk_read_bytes":4586143744,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20237,"usage":{"cpu_time_ns":36787288625,"resident_bytes":30138957824,"physical_bytes":30464941024,"peak_physical_bytes":30490860512,"disk_read_bytes":4588519424,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20347,"usage":{"cpu_time_ns":37007103291,"resident_bytes":30139449344,"physical_bytes":30468709344,"peak_physical_bytes":30490860512,"disk_read_bytes":4621811712,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20457,"usage":{"cpu_time_ns":37104526250,"resident_bytes":30139465728,"physical_bytes":30381841376,"peak_physical_bytes":30492826592,"disk_read_bytes":4622778368,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20567,"usage":{"cpu_time_ns":37688377958,"resident_bytes":30149312512,"physical_bytes":30391819232,"peak_physical_bytes":30492826592,"disk_read_bytes":4622778368,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20677,"usage":{"cpu_time_ns":38275103833,"resident_bytes":30149312512,"physical_bytes":30391819232,"peak_physical_bytes":30492826592,"disk_read_bytes":4629872640,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20787,"usage":{"cpu_time_ns":38856834416,"resident_bytes":30156406784,"physical_bytes":30399060960,"peak_physical_bytes":30492826592,"disk_read_bytes":4634345472,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":20898,"usage":{"cpu_time_ns":39451263125,"resident_bytes":30156406784,"physical_bytes":30399060960,"peak_physical_bytes":30492826592,"disk_read_bytes":4634345472,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":21003,"usage":{"cpu_time_ns":39596631333,"resident_bytes":30156455936,"physical_bytes":30399060960,"peak_physical_bytes":30492826592,"disk_read_bytes":4641406976,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":21113,"usage":{"cpu_time_ns":40182793708,"resident_bytes":30156488704,"physical_bytes":30399060960,"peak_physical_bytes":30492826592,"disk_read_bytes":4641456128,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":21223,"usage":{"cpu_time_ns":40695415708,"resident_bytes":30156505088,"physical_bytes":30399077344,"peak_physical_bytes":30492826592,"disk_read_bytes":4641456128,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":21332,"usage":{"cpu_time_ns":40803346916,"resident_bytes":30156505088,"physical_bytes":30399077344,"peak_physical_bytes":30492826592,"disk_read_bytes":4641456128,"disk_write_bytes":278126592}} +{"event":"test_resource_sample","elapsed_ms":21442,"usage":{"cpu_time_ns":40907531041,"resident_bytes":30179966976,"physical_bytes":30328151008,"peak_physical_bytes":30492826592,"disk_read_bytes":4679618560,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":21548,"usage":{"cpu_time_ns":41013934541,"resident_bytes":30177378304,"physical_bytes":30318926816,"peak_physical_bytes":30492826592,"disk_read_bytes":4679618560,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":21659,"usage":{"cpu_time_ns":41027781583,"resident_bytes":30179311616,"physical_bytes":30383332320,"peak_physical_bytes":30492826592,"disk_read_bytes":4679618560,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":21765,"usage":{"cpu_time_ns":41119768458,"resident_bytes":30177460224,"physical_bytes":30319123424,"peak_physical_bytes":30492826592,"disk_read_bytes":4679618560,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":21875,"usage":{"cpu_time_ns":41156868000,"resident_bytes":30179328000,"physical_bytes":30383348704,"peak_physical_bytes":30492826592,"disk_read_bytes":4679618560,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":21985,"usage":{"cpu_time_ns":41342061583,"resident_bytes":30156095488,"physical_bytes":30509161464,"peak_physical_bytes":30509161464,"disk_read_bytes":4723347456,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22096,"usage":{"cpu_time_ns":41616151666,"resident_bytes":30156390400,"physical_bytes":30524824568,"peak_physical_bytes":30524824568,"disk_read_bytes":4769927168,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22206,"usage":{"cpu_time_ns":41852867791,"resident_bytes":30156685312,"physical_bytes":30527642616,"peak_physical_bytes":30527642616,"disk_read_bytes":4770009088,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22316,"usage":{"cpu_time_ns":42065418000,"resident_bytes":30156980224,"physical_bytes":30530460664,"peak_physical_bytes":30530460664,"disk_read_bytes":4777152512,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22416,"usage":{"cpu_time_ns":42257686500,"resident_bytes":30157275136,"physical_bytes":30533278712,"peak_physical_bytes":30533278712,"disk_read_bytes":4777234432,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22527,"usage":{"cpu_time_ns":42481148333,"resident_bytes":30157570048,"physical_bytes":30535736312,"peak_physical_bytes":30536113144,"disk_read_bytes":4777742336,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22632,"usage":{"cpu_time_ns":42659996041,"resident_bytes":30157864960,"physical_bytes":30538554360,"peak_physical_bytes":30538570744,"disk_read_bytes":4783755264,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22739,"usage":{"cpu_time_ns":42862177166,"resident_bytes":30158159872,"physical_bytes":30541372408,"peak_physical_bytes":30541372408,"disk_read_bytes":4784214016,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22849,"usage":{"cpu_time_ns":43088445208,"resident_bytes":30158618624,"physical_bytes":30545828856,"peak_physical_bytes":30545828856,"disk_read_bytes":4784836608,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":22959,"usage":{"cpu_time_ns":43349610208,"resident_bytes":30158913536,"physical_bytes":30548646904,"peak_physical_bytes":30548646904,"disk_read_bytes":4785442816,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23067,"usage":{"cpu_time_ns":43608854250,"resident_bytes":30159208448,"physical_bytes":30551464952,"peak_physical_bytes":30551464952,"disk_read_bytes":4785786880,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23177,"usage":{"cpu_time_ns":43805280375,"resident_bytes":30159503360,"physical_bytes":30554283000,"peak_physical_bytes":30554283000,"disk_read_bytes":4786147328,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23287,"usage":{"cpu_time_ns":44005966125,"resident_bytes":30159929344,"physical_bytes":30558280696,"peak_physical_bytes":30558280696,"disk_read_bytes":4786786304,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23397,"usage":{"cpu_time_ns":44262162083,"resident_bytes":30160224256,"physical_bytes":30561098744,"peak_physical_bytes":30561098744,"disk_read_bytes":4787277824,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23508,"usage":{"cpu_time_ns":44530889250,"resident_bytes":30160519168,"physical_bytes":30563916792,"peak_physical_bytes":30563933176,"disk_read_bytes":4787834880,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23618,"usage":{"cpu_time_ns":44753365875,"resident_bytes":30160814080,"physical_bytes":30566571000,"peak_physical_bytes":30566587384,"disk_read_bytes":4788342784,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23728,"usage":{"cpu_time_ns":44987289625,"resident_bytes":30161108992,"physical_bytes":30569389048,"peak_physical_bytes":30569405432,"disk_read_bytes":4788736000,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23838,"usage":{"cpu_time_ns":45216566166,"resident_bytes":30161403904,"physical_bytes":30572207096,"peak_physical_bytes":30572223480,"disk_read_bytes":4789194752,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":23948,"usage":{"cpu_time_ns":45458169458,"resident_bytes":30161862656,"physical_bytes":30576663544,"peak_physical_bytes":30576663544,"disk_read_bytes":4795437056,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24059,"usage":{"cpu_time_ns":45684483458,"resident_bytes":30162010112,"physical_bytes":30596963320,"peak_physical_bytes":30596979704,"disk_read_bytes":4887220224,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24169,"usage":{"cpu_time_ns":45826783208,"resident_bytes":30162567168,"physical_bytes":30599748600,"peak_physical_bytes":30599748600,"disk_read_bytes":4964274176,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24279,"usage":{"cpu_time_ns":45925905458,"resident_bytes":30162583552,"physical_bytes":30408629240,"peak_physical_bytes":30624177144,"disk_read_bytes":4965617664,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24382,"usage":{"cpu_time_ns":46507525833,"resident_bytes":30165368832,"physical_bytes":30411758584,"peak_physical_bytes":30624177144,"disk_read_bytes":4965617664,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24488,"usage":{"cpu_time_ns":47114579208,"resident_bytes":30165417984,"physical_bytes":30411889656,"peak_physical_bytes":30624177144,"disk_read_bytes":4972318720,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24599,"usage":{"cpu_time_ns":47722864833,"resident_bytes":30165434368,"physical_bytes":30411889656,"peak_physical_bytes":30624177144,"disk_read_bytes":4975120384,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24709,"usage":{"cpu_time_ns":48327934916,"resident_bytes":30165549056,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4977561600,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24815,"usage":{"cpu_time_ns":48436081625,"resident_bytes":30165549056,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4986753024,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":24925,"usage":{"cpu_time_ns":49015098916,"resident_bytes":30165565440,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4993798144,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25036,"usage":{"cpu_time_ns":49613245416,"resident_bytes":30165565440,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4993814528,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25146,"usage":{"cpu_time_ns":50201444791,"resident_bytes":30165614592,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4993814528,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25248,"usage":{"cpu_time_ns":50782855791,"resident_bytes":30165614592,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4994600960,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25359,"usage":{"cpu_time_ns":50855208875,"resident_bytes":30165614592,"physical_bytes":30411922424,"peak_physical_bytes":30624177144,"disk_read_bytes":4996796416,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25465,"usage":{"cpu_time_ns":51429409333,"resident_bytes":30165630976,"physical_bytes":30411938808,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25575,"usage":{"cpu_time_ns":51539702083,"resident_bytes":30165630976,"physical_bytes":30411709432,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":304209920}} +{"event":"test_resource_sample","elapsed_ms":25686,"usage":{"cpu_time_ns":51647167208,"resident_bytes":30191632384,"physical_bytes":30411709432,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":25800,"usage":{"cpu_time_ns":51757197416,"resident_bytes":30191632384,"physical_bytes":30411709432,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":25910,"usage":{"cpu_time_ns":51790196791,"resident_bytes":30193500160,"physical_bytes":30476164088,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26015,"usage":{"cpu_time_ns":51860793958,"resident_bytes":30191648768,"physical_bytes":30411955192,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26125,"usage":{"cpu_time_ns":51925178791,"resident_bytes":30193532928,"physical_bytes":30476196856,"peak_physical_bytes":30624177144,"disk_read_bytes":4996812800,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26235,"usage":{"cpu_time_ns":51976582666,"resident_bytes":30161207296,"physical_bytes":30306589688,"peak_physical_bytes":30624177144,"disk_read_bytes":4997353472,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26346,"usage":{"cpu_time_ns":52086761666,"resident_bytes":30158635008,"physical_bytes":30303902712,"peak_physical_bytes":30624177144,"disk_read_bytes":4997877760,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26456,"usage":{"cpu_time_ns":52140797166,"resident_bytes":30160535552,"physical_bytes":30368177168,"peak_physical_bytes":30624177144,"disk_read_bytes":4997877760,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26566,"usage":{"cpu_time_ns":52166837416,"resident_bytes":30161747968,"physical_bytes":30325447696,"peak_physical_bytes":30624177144,"disk_read_bytes":4997877760,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26676,"usage":{"cpu_time_ns":52270796375,"resident_bytes":30161747968,"physical_bytes":30384987152,"peak_physical_bytes":30624177144,"disk_read_bytes":5015490560,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26786,"usage":{"cpu_time_ns":52371656958,"resident_bytes":30161747968,"physical_bytes":30384987152,"peak_physical_bytes":30624177144,"disk_read_bytes":5015490560,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":26897,"usage":{"cpu_time_ns":52488050708,"resident_bytes":30161747968,"physical_bytes":30384987152,"peak_physical_bytes":30624177144,"disk_read_bytes":5015490560,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27007,"usage":{"cpu_time_ns":52742979291,"resident_bytes":30161846272,"physical_bytes":30431255568,"peak_physical_bytes":30624177144,"disk_read_bytes":5033021440,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27117,"usage":{"cpu_time_ns":53054210000,"resident_bytes":30161993728,"physical_bytes":30434974736,"peak_physical_bytes":30624177144,"disk_read_bytes":5038051328,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27227,"usage":{"cpu_time_ns":53321723708,"resident_bytes":30162141184,"physical_bytes":30439119888,"peak_physical_bytes":30624177144,"disk_read_bytes":5040885760,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27335,"usage":{"cpu_time_ns":53600033875,"resident_bytes":30162337792,"physical_bytes":30444362768,"peak_physical_bytes":30624177144,"disk_read_bytes":5040984064,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27445,"usage":{"cpu_time_ns":53854126750,"resident_bytes":30162485248,"physical_bytes":30448081936,"peak_physical_bytes":30624177144,"disk_read_bytes":5041082368,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27556,"usage":{"cpu_time_ns":54171421458,"resident_bytes":30162632704,"physical_bytes":30452030480,"peak_physical_bytes":30624177144,"disk_read_bytes":5041213440,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27666,"usage":{"cpu_time_ns":54459903458,"resident_bytes":30162780160,"physical_bytes":30455749648,"peak_physical_bytes":30624177144,"disk_read_bytes":5041311744,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27776,"usage":{"cpu_time_ns":54741755500,"resident_bytes":30162976768,"physical_bytes":30460992528,"peak_physical_bytes":30624177144,"disk_read_bytes":5041328128,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27886,"usage":{"cpu_time_ns":55008021375,"resident_bytes":30163124224,"physical_bytes":30465137680,"peak_physical_bytes":30624177144,"disk_read_bytes":5041737728,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":27996,"usage":{"cpu_time_ns":55327138000,"resident_bytes":30163288064,"physical_bytes":30468873232,"peak_physical_bytes":30624177144,"disk_read_bytes":5045161984,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28107,"usage":{"cpu_time_ns":55571821666,"resident_bytes":30163435520,"physical_bytes":30473018384,"peak_physical_bytes":30624177144,"disk_read_bytes":5048455168,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28217,"usage":{"cpu_time_ns":55881069708,"resident_bytes":30163582976,"physical_bytes":30476737552,"peak_physical_bytes":30624177144,"disk_read_bytes":5049126912,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28327,"usage":{"cpu_time_ns":56205873666,"resident_bytes":30163681280,"physical_bytes":30479358992,"peak_physical_bytes":30624177144,"disk_read_bytes":5078896640,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28437,"usage":{"cpu_time_ns":56506929625,"resident_bytes":30163697664,"physical_bytes":30502476816,"peak_physical_bytes":30624177144,"disk_read_bytes":5156802560,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28547,"usage":{"cpu_time_ns":56577683625,"resident_bytes":30164221952,"physical_bytes":30506900496,"peak_physical_bytes":30624177144,"disk_read_bytes":5159899136,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28657,"usage":{"cpu_time_ns":57208882291,"resident_bytes":30166990848,"physical_bytes":30413052944,"peak_physical_bytes":30624177144,"disk_read_bytes":5168140288,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28768,"usage":{"cpu_time_ns":57817709875,"resident_bytes":30167040000,"physical_bytes":30413052944,"peak_physical_bytes":30624177144,"disk_read_bytes":5169434624,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28877,"usage":{"cpu_time_ns":58415020625,"resident_bytes":30167040000,"physical_bytes":30413052944,"peak_physical_bytes":30624177144,"disk_read_bytes":5172547584,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":28988,"usage":{"cpu_time_ns":59005264291,"resident_bytes":30167040000,"physical_bytes":30413052944,"peak_physical_bytes":30624177144,"disk_read_bytes":5172547584,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":29098,"usage":{"cpu_time_ns":59093337875,"resident_bytes":30167040000,"physical_bytes":30413216784,"peak_physical_bytes":30624177144,"disk_read_bytes":5175087104,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":29198,"usage":{"cpu_time_ns":59665016416,"resident_bytes":30167056384,"physical_bytes":30413216784,"peak_physical_bytes":30624177144,"disk_read_bytes":5179658240,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":29309,"usage":{"cpu_time_ns":60259292458,"resident_bytes":30167072768,"physical_bytes":30413233168,"peak_physical_bytes":30624177144,"disk_read_bytes":5179707392,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":29416,"usage":{"cpu_time_ns":60366687875,"resident_bytes":30167072768,"physical_bytes":30413233168,"peak_physical_bytes":30624177144,"disk_read_bytes":5179707392,"disk_write_bytes":342355968}} +{"event":"test_resource_sample","elapsed_ms":29526,"usage":{"cpu_time_ns":60476678000,"resident_bytes":30176428032,"physical_bytes":30413233168,"peak_physical_bytes":30624177144,"disk_read_bytes":5179707392,"disk_write_bytes":350744576}} +{"event":"test_resource_sample","elapsed_ms":29630,"usage":{"cpu_time_ns":60584143166,"resident_bytes":30162436096,"physical_bytes":30322416656,"peak_physical_bytes":30624177144,"disk_read_bytes":5180850176,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":29740,"usage":{"cpu_time_ns":60708120333,"resident_bytes":30162960384,"physical_bytes":30353497104,"peak_physical_bytes":30624177144,"disk_read_bytes":5181718528,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":29848,"usage":{"cpu_time_ns":60799686958,"resident_bytes":30162976768,"physical_bytes":30362442768,"peak_physical_bytes":30624177144,"disk_read_bytes":5190975488,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":29959,"usage":{"cpu_time_ns":60934210333,"resident_bytes":30162976768,"physical_bytes":30362442768,"peak_physical_bytes":30624177144,"disk_read_bytes":5220745216,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30069,"usage":{"cpu_time_ns":61006942583,"resident_bytes":30170054656,"physical_bytes":30369520656,"peak_physical_bytes":30624177144,"disk_read_bytes":5227823104,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30179,"usage":{"cpu_time_ns":61092643750,"resident_bytes":30170071040,"physical_bytes":30369537040,"peak_physical_bytes":30624177144,"disk_read_bytes":5251203072,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30289,"usage":{"cpu_time_ns":61174474250,"resident_bytes":30170071040,"physical_bytes":30369537040,"peak_physical_bytes":30624177144,"disk_read_bytes":5265276928,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30399,"usage":{"cpu_time_ns":61227649875,"resident_bytes":30170071040,"physical_bytes":30369537040,"peak_physical_bytes":30624177144,"disk_read_bytes":5286395904,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30510,"usage":{"cpu_time_ns":61296737041,"resident_bytes":30170071040,"physical_bytes":30369537040,"peak_physical_bytes":30624177144,"disk_read_bytes":5296275456,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30615,"usage":{"cpu_time_ns":61672150000,"resident_bytes":30172348416,"physical_bytes":30380760080,"peak_physical_bytes":30624177144,"disk_read_bytes":5296275456,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30722,"usage":{"cpu_time_ns":61967489916,"resident_bytes":30172856320,"physical_bytes":30380760080,"peak_physical_bytes":30624177144,"disk_read_bytes":5296275456,"disk_write_bytes":369209344}} +{"event":"test_resource_sample","elapsed_ms":30833,"usage":{"cpu_time_ns":62077499208,"resident_bytes":30174543872,"physical_bytes":30380760080,"peak_physical_bytes":30624177144,"disk_read_bytes":5296275456,"disk_write_bytes":377597952}} +{"event":"test_resource_sample","elapsed_ms":30943,"usage":{"cpu_time_ns":62166982708,"resident_bytes":29337665536,"physical_bytes":29333772152,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856}} +{"event":"test_resource_sample","elapsed_ms":31048,"usage":{"cpu_time_ns":62264277083,"resident_bytes":26074611712,"physical_bytes":29324938824,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856}} +{"event":"test_resource_sample","elapsed_ms":31159,"usage":{"cpu_time_ns":62378628833,"resident_bytes":17460772864,"physical_bytes":29284873680,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856}} +{"event":"test_resource_sample","elapsed_ms":31265,"usage":{"cpu_time_ns":62482826916,"resident_bytes":10172383232,"physical_bytes":29252755760,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856}} +{"event":"test_resource_sample","elapsed_ms":31371,"usage":{"cpu_time_ns":62534506250,"resident_bytes":8705425408,"physical_bytes":25881910376,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856}} +{"event":"test_resource_sample","elapsed_ms":31482,"usage":{"cpu_time_ns":62631362208,"resident_bytes":127090688,"physical_bytes":0,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856}} +{"event":"test_resource_summary","elapsed_ms":31586,"peak":{"cpu_time_ns":62631362208,"resident_bytes":30193532928,"physical_bytes":30599748600,"peak_physical_bytes":30624177144,"disk_read_bytes":5296357376,"disk_write_bytes":383737856},"error":null} diff --git a/docs/issue100-vision/full-ssd.log b/docs/issue100-vision/full-ssd.log new file mode 100644 index 0000000..0159c2f --- /dev/null +++ b/docs/issue100-vision/full-ssd.log @@ -0,0 +1,144 @@ + +running 1 test +test engine::metal::deepseek_vision::tests::issue100_full_model_images_cache_and_dspark ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal SSD streaming mode enabled; full model residency and warmup are skipped +ds4: Metal model range 0.47..0.47 GiB is not covered by mapped model views +ds4: Metal model range 1.64..1.64 GiB is not covered by mapped model views +ds4: Metal model range 1.11..1.11 GiB is not covered by mapped model views +ds4: Metal model range 2.19..2.19 GiB is not covered by mapped model views +ds4: Metal model range 3.36..3.36 GiB is not covered by mapped model views +ds4: Metal model range 2.83..2.84 GiB is not covered by mapped model views +ds4: Metal model range 3.85..3.85 GiB is not covered by mapped model views +ds4: Metal model range 5.02..5.02 GiB is not covered by mapped model views +ds4: Metal model range 4.49..4.49 GiB is not covered by mapped model views +ds4: Metal model range 5.44..5.44 GiB is not covered by mapped model views +ds4: Metal model range 6.61..6.61 GiB is not covered by mapped model views +ds4: Metal model range 6.05..6.06 GiB is not covered by mapped model views +ds4: Metal model range 7.13..7.14 GiB is not covered by mapped model views +ds4: Metal model range 8.31..8.31 GiB is not covered by mapped model views +ds4: Metal model range 7.75..7.75 GiB is not covered by mapped model views +ds4: Metal model range 8.81..8.81 GiB is not covered by mapped model views +ds4: Metal model range 9.98..9.99 GiB is not covered by mapped model views +ds4: Metal model range 9.43..9.43 GiB is not covered by mapped model views +ds4: Metal model range 10.42..10.43 GiB is not covered by mapped model views +ds4: Metal model range 11.60..11.60 GiB is not covered by mapped model views +ds4: Metal model range 11.02..11.02 GiB is not covered by mapped model views +ds4: Metal model range 12.18..12.18 GiB is not covered by mapped model views +ds4: Metal model range 13.35..13.35 GiB is not covered by mapped model views +ds4: Metal model range 12.79..12.79 GiB is not covered by mapped model views +ds4: Metal model range 13.85..13.85 GiB is not covered by mapped model views +ds4: Metal model range 15.02..15.02 GiB is not covered by mapped model views +ds4: Metal model range 14.45..14.46 GiB is not covered by mapped model views +ds4: Metal model range 15.58..15.58 GiB is not covered by mapped model views +ds4: Metal model range 16.75..16.75 GiB is not covered by mapped model views +ds4: Metal model range 16.20..16.20 GiB is not covered by mapped model views +ds4: Metal model range 17.23..17.23 GiB is not covered by mapped model views +ds4: Metal model range 18.40..18.40 GiB is not covered by mapped model views +ds4: Metal model range 17.84..17.84 GiB is not covered by mapped model views +ds4: Metal model range 18.68..18.68 GiB is not covered by mapped model views +ds4: Metal model range 19.85..19.85 GiB is not covered by mapped model views +ds4: Metal model range 19.23..19.23 GiB is not covered by mapped model views +ds4: Metal model range 20.54..20.54 GiB is not covered by mapped model views +ds4: Metal model range 21.71..21.72 GiB is not covered by mapped model views +ds4: Metal model range 21.13..21.14 GiB is not covered by mapped model views +ds4: Metal model range 22.19..22.19 GiB is not covered by mapped model views +ds4: Metal model range 23.36..23.36 GiB is not covered by mapped model views +ds4: Metal model range 22.77..22.77 GiB is not covered by mapped model views +ds4: Metal model range 23.95..23.95 GiB is not covered by mapped model views +ds4: Metal model range 25.12..25.13 GiB is not covered by mapped model views +ds4: Metal model range 24.55..24.56 GiB is not covered by mapped model views +ds4: Metal model range 25.53..25.54 GiB is not covered by mapped model views +ds4: Metal model range 26.71..26.71 GiB is not covered by mapped model views +ds4: Metal model range 26.11..26.11 GiB is not covered by mapped model views +ds4: Metal model range 27.39..27.39 GiB is not covered by mapped model views +ds4: Metal model range 28.56..28.56 GiB is not covered by mapped model views +ds4: Metal model range 28.00..28.01 GiB is not covered by mapped model views +ds4: Metal model range 29.06..29.06 GiB is not covered by mapped model views +ds4: Metal model range 30.23..30.23 GiB is not covered by mapped model views +ds4: Metal model range 29.67..29.68 GiB is not covered by mapped model views +ds4: Metal model range 30.43..30.43 GiB is not covered by mapped model views +ds4: Metal model range 31.60..31.60 GiB is not covered by mapped model views +ds4: Metal model range 30.95..30.96 GiB is not covered by mapped model views +ds4: Metal model range 32.47..32.47 GiB is not covered by mapped model views +ds4: Metal model range 33.64..33.64 GiB is not covered by mapped model views +ds4: Metal model range 33.09..33.10 GiB is not covered by mapped model views +ds4: Metal model range 34.15..34.16 GiB is not covered by mapped model views +ds4: Metal model range 35.33..35.33 GiB is not covered by mapped model views +ds4: Metal model range 34.78..34.78 GiB is not covered by mapped model views +ds4: Metal model range 35.77..35.77 GiB is not covered by mapped model views +ds4: Metal model range 36.94..36.94 GiB is not covered by mapped model views +ds4: Metal model range 36.37..36.37 GiB is not covered by mapped model views +ds4: Metal model range 37.39..37.39 GiB is not covered by mapped model views +ds4: Metal model range 38.56..38.56 GiB is not covered by mapped model views +ds4: Metal model range 37.97..37.98 GiB is not covered by mapped model views +ds4: Metal model range 39.26..39.26 GiB is not covered by mapped model views +ds4: Metal model range 40.43..40.43 GiB is not covered by mapped model views +ds4: Metal model range 39.89..39.90 GiB is not covered by mapped model views +ds4: Metal model range 40.70..40.70 GiB is not covered by mapped model views +ds4: Metal model range 41.87..41.87 GiB is not covered by mapped model views +ds4: Metal model range 41.26..41.27 GiB is not covered by mapped model views +ds4: Metal model range 42.53..42.53 GiB is not covered by mapped model views +ds4: Metal model range 43.70..43.70 GiB is not covered by mapped model views +ds4: Metal model range 43.13..43.13 GiB is not covered by mapped model views +ds4: Metal model range 44.05..44.05 GiB is not covered by mapped model views +ds4: Metal model range 45.22..45.22 GiB is not covered by mapped model views +ds4: Metal model range 44.61..44.61 GiB is not covered by mapped model views +ds4: Metal model range 46.04..46.04 GiB is not covered by mapped model views +ds4: Metal model range 47.21..47.21 GiB is not covered by mapped model views +ds4: Metal model range 46.68..46.68 GiB is not covered by mapped model views +ds4: Metal model range 47.54..47.55 GiB is not covered by mapped model views +ds4: Metal model range 48.72..48.72 GiB is not covered by mapped model views +ds4: Metal model range 48.14..48.14 GiB is not covered by mapped model views +ds4: Metal model range 49.01..49.01 GiB is not covered by mapped model views +ds4: Metal model range 50.18..50.19 GiB is not covered by mapped model views +ds4: Metal model range 49.54..49.55 GiB is not covered by mapped model views +ds4: Metal model range 50.94..50.94 GiB is not covered by mapped model views +ds4: Metal model range 52.11..52.12 GiB is not covered by mapped model views +ds4: Metal model range 51.54..51.54 GiB is not covered by mapped model views +ds4: Metal model range 52.39..52.39 GiB is not covered by mapped model views +ds4: Metal model range 53.56..53.56 GiB is not covered by mapped model views +ds4: Metal model range 52.92..52.92 GiB is not covered by mapped model views +ds4: Metal model range 54.16..54.16 GiB is not covered by mapped model views +ds4: Metal model range 55.33..55.34 GiB is not covered by mapped model views +ds4: Metal model range 54.72..54.72 GiB is not covered by mapped model views +ds4: Metal model range 56.08..56.08 GiB is not covered by mapped model views +ds4: Metal model range 57.25..57.26 GiB is not covered by mapped model views +ds4: Metal model range 56.70..56.70 GiB is not covered by mapped model views +ds4: Metal model range 57.64..57.64 GiB is not covered by mapped model views +ds4: Metal model range 58.81..58.81 GiB is not covered by mapped model views +ds4: Metal model range 58.22..58.22 GiB is not covered by mapped model views +ds4: Metal model range 59.45..59.45 GiB is not covered by mapped model views +ds4: Metal model range 60.62..60.63 GiB is not covered by mapped model views +ds4: Metal model range 60.07..60.07 GiB is not covered by mapped model views +ds4: Metal model range 61.01..61.01 GiB is not covered by mapped model views +ds4: Metal model range 62.18..62.19 GiB is not covered by mapped model views +ds4: Metal model range 61.59..61.60 GiB is not covered by mapped model views +ds4: Metal model range 62.77..62.77 GiB is not covered by mapped model views +ds4: Metal model range 63.94..63.94 GiB is not covered by mapped model views +ds4: Metal model range 63.37..63.37 GiB is not covered by mapped model views +ds4: Metal model range 64.15..64.16 GiB is not covered by mapped model views +ds4: Metal model range 65.33..65.33 GiB is not covered by mapped model views +ds4: Metal model range 64.67..64.68 GiB is not covered by mapped model views +ds4: Metal model range 66.07..66.07 GiB is not covered by mapped model views +ds4: Metal model range 67.24..67.24 GiB is not covered by mapped model views +ds4: Metal model range 66.65..66.65 GiB is not covered by mapped model views +ds4: Metal model range 67.62..67.62 GiB is not covered by mapped model views +ds4: Metal model range 68.79..68.79 GiB is not covered by mapped model views +ds4: Metal model range 68.16..68.17 GiB is not covered by mapped model views +ds4: Metal model range 69.51..69.52 GiB is not covered by mapped model views +ds4: Metal model range 70.69..70.69 GiB is not covered by mapped model views +ds4: Metal model range 70.11..70.12 GiB is not covered by mapped model views +ds4: Metal model range 71.09..71.09 GiB is not covered by mapped model views +ds4: Metal model range 72.26..72.26 GiB is not covered by mapped model views +ds4: Metal model range 71.66..71.66 GiB is not covered by mapped model views +red: The image shows a solid red square. +DSpark drafted=30 accepted=0 +blue: The image shows a solid blue circle. +pair: The first shape is red, and the second shape is blue. +tool image: The color in the image is blue. +issue100: production rendering, image recognition, multi-image/tool input, same-layout image invalidation, disk/warm KV and DSpark target equivalence passed +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 31.46s diff --git a/docs/issue100-vision/glm-control.log b/docs/issue100-vision/glm-control.log new file mode 100644 index 0000000..fab5f29 --- /dev/null +++ b/docs/issue100-vision/glm-control.log @@ -0,0 +1,58 @@ + +running 1 test +test engine::metal::glm::tests::glm53_two_row_verifier_restores_recurrent_state ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal model views created in 2.025 ms, residency requested in 9751.845 ms, warmup 4.026 ms (mapped 92026.06 MiB from offset 9.07 MiB) +ds4: Metal mapped mmaped model as 2 overlapping shared buffers +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +partial prefill HC workspace guards passed +partial prefill HC workspace guards passed +partial prefill HC workspace guards passed +partial prefill HC workspace guards passed +partial prefill HC workspace guards passed +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +ds4: GLM MTP verify2 at 12: 44.202 ms, draft 729, target 729 +ds4: GLM MTP draft 387 at position 13 in 3.1 ms +verify2 accepted and rewind keep=0 passed +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +ds4: GLM MTP verify2 at 12: 36.927 ms, draft 729, target 729 +ds4: GLM MTP draft 387 at position 13 in 2.8 ms +verify2 accepted and rewind keep=1 passed +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +ds4: GLM MTP verify2 at 12: 36.681 ms, draft 730, target 729 +ds4: GLM MTP draft 5733 at position 12 in 2.1 ms +verify2 rejected and scalar replay state passed +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +ds4: GLM MTP draft 5733 at position 12 in 3.1 ms +verify2 scalar boundary cap=1 cancelled=false passed +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +ds4: GLM MTP draft 5733 at position 12 in 3.0 ms +verify2 scalar boundary cap=2 cancelled=true passed +verify2 prefill 0 +verify2 prefill 4 +verify2 prefill 8 +verify2 prefill 12 +ds4: GLM MTP draft 5733 at position 12 in 3.0 ms +engine-lifetime counters across new and restored chats passed +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 568 filtered out; finished in 14.15s diff --git a/docs/issue100-vision/glm-tokenizer-control.log b/docs/issue100-vision/glm-tokenizer-control.log new file mode 100644 index 0000000..16ffbda --- /dev/null +++ b/docs/issue100-vision/glm-tokenizer-control.log @@ -0,0 +1,5 @@ + +running 1 test +test engine::tokenizer::tests::ds4_chat_matches_original_session_tokens ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 0.22s diff --git a/docs/issue100-vision/images/blue-circle.png b/docs/issue100-vision/images/blue-circle.png new file mode 100644 index 0000000..60ab1a4 Binary files /dev/null and b/docs/issue100-vision/images/blue-circle.png differ diff --git a/docs/issue100-vision/images/red-square.png b/docs/issue100-vision/images/red-square.png new file mode 100644 index 0000000..1b8dfee Binary files /dev/null and b/docs/issue100-vision/images/red-square.png differ diff --git a/docs/issue100-vision/intake.log b/docs/issue100-vision/intake.log new file mode 100644 index 0000000..e757859 --- /dev/null +++ b/docs/issue100-vision/intake.log @@ -0,0 +1,8 @@ + +running 1 test +test model::transfer::tests::issue100_managed_intake ... verified DeepSeek-V4-Flash-Vision-Encoder.gguf 932857760 bytes +verified DeepSeek-V4-Flash-Vision-Exp-DSpark-support.gguf 5989114528 bytes +verified DeepSeek-V4-Flash-Vision-Exp-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8.gguf 86720111776 bytes +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 622.11s diff --git a/docs/issue100-vision/metadata.log b/docs/issue100-vision/metadata.log new file mode 100644 index 0000000..b313f0d --- /dev/null +++ b/docs/issue100-vision/metadata.log @@ -0,0 +1,22 @@ + Compiling ds4-server v0.1.0 (/Users/gb/Projects/DS4Server) + Finished `test` profile [unoptimized + debuginfo] target(s) in 4.00s +warning: the following packages contain code that will be rejected by a future version of Rust: block v0.1.6 +note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1` + Running unittests src/main.rs (target/debug/deps/ds4_server-d15236c42a8179f4) + +running 1 test +test engine::validation::tests::issue100_checkpoint_metadata_is_strict ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 568 filtered out; finished in 0.01s + + Running unittests tools/qwen38-artifacts.rs (target/debug/deps/qwen38_artifacts-3a34dfcd9c4f3de8) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s + + Running unittests tools/test-supervisor.rs (target/debug/deps/test_supervisor-978addc9e5e8dbda) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s diff --git a/docs/issue100-vision/metal-primitives-resources.jsonl b/docs/issue100-vision/metal-primitives-resources.jsonl new file mode 100644 index 0000000..4e05e9d --- /dev/null +++ b/docs/issue100-vision/metal-primitives-resources.jsonl @@ -0,0 +1,40 @@ +{"event":"test_supervisor_started","pid":19964,"limit_bytes":4294967296,"start_seconds":60.0,"idle_seconds":120.0,"sample_ms":100} +{"event":"test_resource_sample","elapsed_ms":0,"usage":{"cpu_time_ns":155791,"resident_bytes":671744,"physical_bytes":196680,"peak_physical_bytes":213064,"disk_read_bytes":0,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":107,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":217,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":319,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":427,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":537,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":647,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":757,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":866,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":976,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1083,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1187,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1298,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1400,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1501,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1610,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1712,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1816,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":1918,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2025,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2132,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2243,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2344,"usage":{"cpu_time_ns":2620666,"resident_bytes":32768,"physical_bytes":81992,"peak_physical_bytes":98376,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2447,"usage":{"cpu_time_ns":2858791,"resident_bytes":376832,"physical_bytes":163912,"peak_physical_bytes":163912,"disk_read_bytes":1597440,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2558,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2666,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2770,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2877,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":2987,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3097,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3207,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3316,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3426,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3536,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3644,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3746,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3856,"usage":{"cpu_time_ns":32403000,"resident_bytes":27983872,"physical_bytes":11141864,"peak_physical_bytes":12206824,"disk_read_bytes":3067904,"disk_write_bytes":0}} +{"event":"test_resource_sample","elapsed_ms":3960,"usage":{"cpu_time_ns":38735291,"resident_bytes":46923776,"physical_bytes":21824280,"peak_physical_bytes":24675072,"disk_read_bytes":3256320,"disk_write_bytes":4456448}} +{"event":"test_resource_summary","elapsed_ms":4067,"peak":{"cpu_time_ns":38735291,"resident_bytes":46923776,"physical_bytes":21824280,"peak_physical_bytes":24675072,"disk_read_bytes":3256320,"disk_write_bytes":4456448},"error":null} diff --git a/docs/issue100-vision/metal-primitives.log b/docs/issue100-vision/metal-primitives.log new file mode 100644 index 0000000..89fce00 --- /dev/null +++ b/docs/issue100-vision/metal-primitives.log @@ -0,0 +1,11 @@ + +running 1 test +test engine::metal::deepseek_vision::tests::issue100_visual_routing_and_attention ... ds4: Metal device Apple M5 Max, 128.00 GiB RAM +ds4: Metal 4 tensor API enabled for Tensor kernels +ds4: drift-patch flags hc_stable=on norm_unify=on kv_raw_f32=off rope_exp2_log2=off math_safe=off tensor_matmul=on +ds4: Metal model views created in 0.022 ms, residency requested in 0.060 ms, warmup 4.036 ms (mapped 0.02 MiB from offset 0.00 MiB) +ds4: Metal mapped mmaped model as 1 overlapping shared buffers +issue100 visual routing: hash/text/image rows and weights pass; raw/mixed/index-masked visual attention with wrapped KV passes +ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 1.52s diff --git a/docs/issue100-vision/official-encoder.log b/docs/issue100-vision/official-encoder.log new file mode 100644 index 0000000..182030a --- /dev/null +++ b/docs/issue100-vision/official-encoder.log @@ -0,0 +1 @@ +{"source_revision": "e46e16bf6035c6f317eb2ac7458eb0362926d402", "ds4_revision": "0aaea5a238fb41a35106a551e73c8409dfb751ac", "torch": "2.14.1", "device": "mps", "shape": [100, 4096], "max_abs": 0.102294921875, "rmse": 0.005685029085725546, "reference_rms": 0.05688675865530968, "cosine": 0.9950079321861267} diff --git a/docs/issue100-vision/tests.log b/docs/issue100-vision/tests.log new file mode 100644 index 0000000..5453b90 --- /dev/null +++ b/docs/issue100-vision/tests.log @@ -0,0 +1,613 @@ + Compiling ds4-server v0.1.0 (/Users/gb/Projects/DS4Server) + Finished `test` profile [unoptimized + debuginfo] target(s) in 5.00s +warning: the following packages contain code that will be rejected by a future version of Rust: block v0.1.6 +note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1` + Running unittests src/main.rs (target/debug/deps/ds4_server-d15236c42a8179f4) + +running 570 tests +test agent::tests::ai_risk_response_requires_structured_risk_and_reason ... ok +test agent::tests::anchored_edits_require_unique_head_and_tail ... ok +test a2ui::tests::streaming_parser_only_accepts_complete_a2ui_jsonl_lines ... ok +test agent::tests::ai_shell_risk_assessment_receives_the_dev_brain_root ... ok +test a2ui::tests::embedded_catalog_and_validator_cover_the_same_components ... ok +test agent::tests::dev_brain_paths_are_trusted_by_shell_risk_assessment ... ok +test a2ui::tests::basic_formatters_follow_catalog_examples ... ok +test a2ui::tests::catalog_validation_rejects_invalid_variants_and_function_arguments ... ok +test a2ui::tests::catalog_accepts_pie_donut_and_heatmap_charts ... ok +test a2ui::tests::dismissal_boundaries_replay_history_and_a_fresh_active_epoch ... ok +test a2ui::tests::lifecycle_updates_incrementally_and_json_pointer_upserts ... ok +test a2ui::tests::latest_created_surface_is_the_active_surface ... ok +test agent::tests::present_svg_parsers_recover_exact_source_for_both_model_transports ... ok +test agent::tests::ralph_loop_stops_on_cap_blocker_failure_and_cancellation ... ok +test a2ui::tests::catalog_validation_and_actions_use_current_local_data ... ok +test a2ui::tests::all_basics_compose_into_one_surface ... ok +test agent::tests::model_adapters_preserve_primitives_and_report_recoverable_syntax_errors ... ok +test agent::tests::ralph_report_semantics_and_handoff_bound_are_enforced ... ok +test agent::tests::risky_shell_commands_require_one_time_approval ... ok +test a2ui::tests::v1_action_and_function_round_trips_follow_current_schema ... ok +test agent::tests::scripted_ralph_backend_gets_fresh_prompts_and_only_validated_handoffs ... ok +test agent::tests::project_boundary_rejects_parent_paths ... ok +test agent::tests::generated_schemas_match_the_executable_tool_contracts ... ok +test agent::tests::ralph_report_schemas_are_child_only_and_parse_for_both_models ... ok +test agent::tests::repeated_call_advisories_use_canonical_arguments_and_reset_per_user_turn ... ok +test a2ui::export::tests::static_svg_renderer_covers_the_complete_catalog ... ok +test agent::tests::stored_tool_cards_restore_ai_approval_reasons_by_call ... ok +test agent::tests::tool_output_keeps_terminal_lines_but_parameter_summaries_do_not ... ok +test agent::tests::present_svg_executes_without_side_effects_and_maps_only_completed_cards ... ok +test agent::web::tests::browser_helpers_preserve_ds4_wire_values ... ok +test agent::web::tests::headless_browser_extracts_a_local_page ... ignored, requires an installed Chrome browser +test agent::tests::prompts_and_parsers_expose_the_reference_tool_set ... ok +test agent::tests::validation_is_precise_and_prevents_side_effects_until_corrected ... ok +test app::generation::tests::attached_image_marker_is_model_only ... ok +test agent::tests::glob_confinement_skips_symlinks_and_honors_cancellation ... ok +test agent::tests::present_svg_validation_enforces_the_static_vector_boundary ... ok +test agent::tests::successful_structured_file_tools_report_project_paths ... ok +test agent::tests::glob_finds_all_regular_file_names_deterministically_without_reading_them ... ok +test agent::tests::standard_agent_skills_are_discovered_prompted_and_read_only ... ok +test agent::tests::shell_commands_skip_user_startup_files ... ok +test app::generation::tests::plain_chat_uses_the_same_unmodified_prompt_in_ui_and_headless ... ok +test app::generation::tests::queued_guidance_is_one_reference_style_user_turn ... ok +test app::generation::tests::title_context_starts_at_the_first_user_and_ignores_system_rows ... ok +test app::generation::tests::turn_summary_accumulates_model_continuations ... ok +test agent::tests::standard_file_tools_can_maintain_only_managed_dev_brain_pages ... ok +test agent::tests::shell_environment_is_loaded_once_from_login_and_interactive_startup_files ... ok +test app::git::tests::configured_git_diff_options_change_the_rendered_patch ... ok +test app::git::tests::unified_diff_parser_aligns_replacements_for_split_view ... ok +test agent::tests::ralph_round_contexts_are_unique_and_child_jobs_are_cleaned_up ... ok +test app::git::tests::unstage_works_before_the_first_commit ... ok +test app::preferences::tests::runtime_config_updates_after_lock_poisoning ... ok +test app::git::tests::libgit2_status_merges_staged_and_worktree_changes ... ok +test app::projects::tests::discarding_a_session_removes_every_checkpoint_stage ... ok +test app::projects::tests::draft_titles_follow_the_stored_session_count ... ok +test agent::tests::denial_and_stop_cancel_commands_awaiting_approval ... ok +test app::projects::tests::session_states_are_exclusive_and_ordered ... ok +test app::projects::tests::sweeping_removes_checkpoints_of_sessions_that_no_longer_exist ... ok +test app::projects::tests::git_state_lists_and_switches_local_branches ... ok +test app::tests::background_activity_drives_polling_indicators_and_quit_confirmation ... ok +test agent::tests::shell_environment_probe_stops_blocked_startup_files ... ok +test app::git::tests::origin_fetch_pull_and_push_use_the_current_branch ... ok +test app::tests::chat_html_export_embeds_loaded_a2ui_images_as_data_urls ... ok +test agent::tests::oversized_shell_output_is_bounded_and_retained_until_cleanup ... ok +test app::tests::chat_only_follows_updates_while_near_the_tail ... ok +test app::tests::ds4_gib_and_streaming_cache_inputs_are_typed ... ok +test app::tests::one_shot_titles_are_reduced_to_a_single_sidebar_line ... ok +test app::tests::owned_runtime_paths_stay_in_application_support ... ok +test app::tests::preferences_shortcut_and_dspark_support_are_explicit ... ok +test app::tests::source_does_not_depend_on_a_sibling_ds4_checkout ... ok +test app::tests::stored_session_always_selects_its_model ... ok +test app::tests::tab_scrolls_a_field_back_into_the_preferences_viewport ... ok +test app::view::a2ui::tests::renderer_input_helpers_cover_dates_numbers_choices_and_weights ... ok +test app::view::chat::tests::chat_divider_groups_token_counts_by_thousands ... ok +test app::view::chat::tests::composer_uses_shift_enter_for_line_breaks ... ok +test app::view::git::tests::split_diff_width_covers_the_longest_side_and_tabs ... ok +test app::preferences::tests::multiline_system_prompt_round_trips_through_the_editor ... ok +test app::view::tests::action_buttons_share_geometry ... ok +test app::view::tests::context_pie_wedge_follows_the_fill_grade ... ok +test app::view::tests::download_measurements_are_readable ... ok +test app::view::tests::raised_surfaces_stay_dark ... ok +test compaction::tests::a_new_summary_replaces_the_previous_rebuild_summary ... ok +test compaction::tests::hard_trigger_and_retry_reserve_match_the_reference ... ok +test compaction::tests::soft_trigger_matches_reference_thresholds ... ok +test compaction::tests::summary_budget_keeps_answer_room_and_rejects_exhausted_contexts ... ok +test compaction::tests::summary_control_markup_is_private ... ok +test compaction::tests::tail_prefers_the_first_user_boundary_after_target ... ok +test config::tests::defaults_write_an_empty_file_and_reload_unchanged ... ok +test app::generation::tests::legacy_generation_rows_collapse_into_their_user_turn ... ok +test app::preferences::tests::selectors_rehydrate_their_generation_and_model_profiles ... ok +test app::tests::chat_export_preserves_completed_svg_and_keeps_failures_generic ... ok +test app::generation::tests::forced_compaction_requires_new_visible_chat ... ok +test config::tests::deprecated_flash_config_migrates_and_persists_only_0731 ... ok +test app::generation::tests::model_history_uses_persisted_content_without_changing_the_transcript ... ok +test config::tests::model_profiles_expose_only_their_upstream_thinking_modes ... ok +test app::tests::chat_export_formats_visible_turns_reasoning_and_bash_tools ... ok +test app::tests::assistant_stream_keeps_reasoning_separate_from_the_answer ... ok +test config::tests::legacy_flat_model_settings_are_dropped_but_the_prompt_is_kept ... ok +test config::tests::invalid_files_are_reported_instead_of_reset ... ok +test app::tests::chat_html_export_is_safe_standalone_and_renders_embedded_surfaces ... ok +test config::tests::explicit_0731_dspark_opt_out_survives_the_default ... ok +test config::tests::only_changed_values_are_written_and_read_back ... ok +test app::view::stats::tests::current_session_summary_uses_persisted_chat_facts ... ok +test app::generation::tests::assistant_markdown_keeps_llm_tables ... ok +test app::generation::tests::last_compaction_selects_its_tail_without_hiding_history ... ok +test app::generation::tests::complete_tool_call_in_reasoning_gets_one_correction ... ok +test app::generation::tests::extension_context_deduplicates_session_start_and_rearms_after_compaction ... ok +test config::tests::qwen_selection_and_native_defaults_survive_restart ... ok +test app::generation::tests::a2ui_auto_switch_waits_for_a_renderable_surface ... ok +test dev_brain::tests::revision_fixture_preserves_numeric_looking_strings ... ok +test dev_brain::tests::path_confinement_and_unrelated_notes_are_preserved ... ok +test dev_brain::tests::skill_index_migration_does_not_overwrite_an_unrelated_root_note ... ok +test dsml::tests::parses_reference_and_legacy_dsml_syntaxes ... ok +test dsml::tests::recovers_complete_invokes_before_an_incomplete_one ... ok +test dsml::tests::recovers_complete_invokes_without_the_outer_closing_tag ... ok +test engine::deepseek_vision::tests::issue100_ds4_image_layout_and_attention_vectors ... ok +test database::tests::context_work_reactivates_an_archived_session ... ok +test database::tests::sessions_are_ordered_by_recent_use_within_sidebar_groups ... ok +test engine::ds4_sampling::tests::heap_fallback_preserves_rng_and_signed_zero_ties ... ok +test engine::gguf::tests::accepts_exact_mxfp4_block_layout ... ok +test engine::gguf::tests::keeps_unknown_tensor_kinds_rejected ... ok +test engine::gguf::tests::mxfp4_scalar_dot_matches_independent_values ... ok +test engine::gguf::tests::maps_metadata_and_tensor_payload_without_copying_weights ... ok +test engine::gguf::tests::rejects_mxfp4_dimension_overflow ... ok +test engine::gguf::tests::rejects_mxfp4_rows_that_are_not_block_aligned ... ok +test database::tests::inline_svg_tool_call_survives_session_switch_compaction_and_reopen ... ok +test engine::gguf::tests::rejects_truncated_mxfp4_payload ... ok +test database::tests::a2ui_dismissal_persists_a_fresh_surface_boundary ... ok +test engine::metal::deepseek_vision::tests::issue100_encoder_real_weights ... ignored, requires the pinned Vision-Exp sidecar and Apple Metal +test engine::metal::deepseek_vision::tests::issue100_full_model_images_cache_and_dspark ... ignored, requires the verified Vision-Exp managed artifacts and 110 GiB of Apple unified memory +test engine::metal::deepseek_vision::tests::issue100_visual_routing_and_attention ... ignored, requires Apple Metal; isolated visual routing and attention primitives +test engine::metal::glm::tests::dsa_indexer_schedule_matches_the_reference ... ok +test engine::metal::glm::tests::glm53_m5_text_performance_gate ... ignored, requires the installed GLM 5.3 Flash Q2 checkpoint and Apple Metal +test engine::metal::glm::tests::glm53_m5_vision_performance_and_oracle_gate ... ignored, requires GLM 5.3 Q2, its vision sidecar, a fixed image, and Apple Metal +test engine::metal::glm::tests::glm53_reference_logits_replay_separates_sampling_from_execution ... ignored, requires the installed GLM 5.3 Flash Q2 checkpoint and Apple Metal +test engine::metal::glm::tests::glm53_reference_prompt_tokens_match_shared_runtime ... ignored, installed GLM and DS4SERVER_GLM_REFERENCE_LOG from the standalone reference +test engine::metal::glm::tests::glm53_two_row_verifier_restores_recurrent_state ... ignored, installed GLM 5.3 and Metal; run under test-supervisor +test engine::metal::glm::tests::glm_byte_budget_reserves_prefill_before_dynamic_experts ... ok +test engine::metal::glm::tests::glm_mtp_preserves_target_tokens_and_drafts ... ignored, requires the 197 GiB GLM 5.2 checkpoint and Apple Metal +test engine::metal::glm::tests::glm_prefill_schedule_matches_ds4_boundaries ... ok +test engine::metal::glm::tests::glm_resident_decode_flushes_periodically_without_a_final_or_ssd_flush ... ok +test engine::metal::glm::tests::repeated_glm_prompt_rewinds_one_token_for_logits ... ok +test engine::metal::glm::tests::resident_and_streamed_glm_match_ds4_decode_oracles ... ignored, requires the 197 GiB GLM 5.2 checkpoint and Apple Metal +test engine::metal::glm::tests::streamed_glm_uses_ds4_indexed_prefill_for_long_prompts ... ignored, requires the 197 GiB GLM 5.2 checkpoint and Apple Metal +test engine::metal::gpu::qwen_mtplx_allocation_tests::backend_allocation_policy_is_explicit_inside_and_outside_qwen_scope ... ignored, requires Apple Metal; allocation only, no GPU dispatch or model +test engine::metal::gpu::qwen_mtplx_allocation_tests::dispatch_retains_allocation_without_changing_data_donation ... ignored, requires Apple Metal, no model weights +test engine::metal::gpu::qwen_mtplx_allocation_tests::empty_storage_preserves_data_identity_views_and_zero_allocator_usage ... ignored, requires Apple Metal context; empty descriptors, no GPU allocation +test engine::metal::gpu::qwen_mtplx_allocation_tests::mtplx_tensor_cpu_access_releases_native_storage ... ignored, requires Apple Metal; CPU buffer access only, no GPU dispatch or model +test engine::metal::markov::tests::installed_markov_worker_dispatch ... ignored, requires installed 0731 DSpark support; CPU dispatch diagnostic, not parity acceptance +test database::tests::migrations_and_crud_work ... ok +test engine::kvstore::tests::opening_reclaims_checkpoints_the_index_cannot_reach ... ok +test engine::metal::markov::tests::markov_workers_reuse_threads_and_buffers_with_ordered_ties ... ok +test engine::metal::power_throttle_keeps_ds4_ewma_and_full_power_bypass ... ok +test engine::metal::qwen::tests::affine_split_k_matches_reference_shapes_and_scratch_bound ... ok +test engine::metal::qwen::tests::dense_attention_crossover_is_phase_specific ... ok +test engine::metal::qwen::tests::qwen_affine_qmv_and_qmm_match_mlx_reference ... ignored, requires the pinned Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_affine_qmv_profile ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_core_boundary_and_checkpoint_are_stable ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_fused_gdn_input_rows_match_separate_projections ... ignored, requires the pinned Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_gdn_conv_norm_rows_matches_mtplx ... ignored, requires Apple Metal +test engine::metal::qwen::tests::qwen_gdn_delta_rows_matches_mtplx ... ignored, requires Apple Metal +test engine::metal::qwen::tests::qwen_layer_profile ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qsa_selection_and_capacity_cover_the_full_native_window ... ok +test engine::metal::qwen::tests::qwen_lazy_target_probabilities_match_batched_sampling ... ignored, requires the pinned Qwen metadata and Apple Metal; no model inference +test engine::metal::qwen::tests::qwen_m5_performance_gate ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_metal_primitives_match_reference_vectors ... ignored, requires Apple Metal +test engine::metal::qwen::tests::qwen_mtp_matches_target_and_restores_its_cache ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_ple_gather_copies_only_the_requested_row ... ok +test engine::metal::qwen::tests::qwen_qsa_dense_mask_accepts_unsorted_blocks ... ignored, requires Apple Metal +test engine::metal::qwen::tests::qwen_qsa_full_context_preallocation_succeeds ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_qsa_logical_depth_oracles_cover_prefill_decode_and_state ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_qsa_real_layer_executes_the_first_sparse_token ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_qsa_selection_and_attention_remain_bounded_at_native_depths ... ignored, requires Apple Metal and allocates the full QSA score window +test engine::metal::qwen::tests::qwen_reference_layer_trace ... ignored, diagnostic trace against the pinned MLX-LM reference +test engine::metal::qwen::tests::qwen_reference_sdpa_trace ... ignored, diagnostic comparison with captured MTPLX SDPA inputs +test engine::metal::qwen::tests::qwen_reference_stateful_trace ... ignored, diagnostic trace against the pinned MLX-LM reference +test engine::metal::qwen::tests::qwen_reference_verify_trace ... ignored, diagnostic trace against the pinned MTPLX verifier +test engine::metal::qwen::tests::qwen_sorted_routed_prefill_matches_grouped_output ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_stage_counters_preserve_async_batches ... ignored, requires pinned Qwen embeddings and Apple Metal; stage profiler opt-in +test engine::metal::qwen::tests::qwen_target_submission_profile ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen::tests::qwen_verify_layer_profile ... ignored, requires the pinned 105 GB Qwen artifact set and Apple Metal +test engine::metal::qwen_mtplx::allocator::mtplx_allocations_preserve_logical_bounds_with_reference_rounding ... ignored, requires Apple Metal, no model weights +test engine::kvstore::tests::budget_evicts_the_least_valuable_unprotected_entry ... ok +test engine::metal::qwen::tests::qwen_ple_hash_contract_matches_golden_boundaries ... ok +test engine::metal::qwen_mtplx::allocator::mtplx_native_allocator_heap_limits_and_gpu_lifetime ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx::allocator::mtplx_native_allocator_replays_reference_allocations ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::ar::mtplx_ar_driver_matches_reference ... ignored, requires Apple Metal; unchanged MTPLX generate_ar orchestration oracle +test engine::metal::qwen_mtplx::array::mtplx_array_detach_clears_every_siblings_inputs_and_primitive ... ok +test engine::metal::qwen_mtplx::array::mtplx_array_materialization_keeps_geometry_and_rejects_short_storage ... ignored, requires Apple Metal; typed materialized descriptors without model weights +test engine::metal::qwen_mtplx::array::mtplx_array_layout_preserves_dtype_shape_stride_and_materialization_flags ... ok +test engine::metal::qwen_mtplx::array::mtplx_dtype_promotion_matches_all_reference_pairs ... ok +test engine::metal::qwen_mtplx::attention::mtplx_qsa_dense_mask_graph_matches_reference ... ignored, requires Apple Metal; original MTPLX block mask including sentinel and tails +test engine::metal::qwen_mtplx::attention::mtplx_qsa_eager_array_graph_matches_reference ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::attention::mtplx_qsa_prefill_gather_graph_matches_reference ... ignored, requires Apple Metal; actual MTPLX tiled prefill gather +test engine::metal::qwen_mtplx::attention::mtplx_qsa_rows_gather_graph_matches_reference ... ignored, requires Apple Metal; actual MTPLX QSA rows-gather attention +test engine::metal::qwen_mtplx::attention::mtplx_sdpa_fallback_graph_matches_reference ... ignored, requires Apple M5 Max; actual Qwen SDPA fallback reference +test engine::metal::qwen_mtplx::binary::mtplx_graph_binary_preserves_reference_branches ... ignored, requires Apple Metal; binary graph broadcast/layout and donation branches +test engine::metal::qwen_mtplx::binding::mtplx_installed_module_binding_matches_reference ... ignored, requires installed DS4_QWEN38_ARTIFACTS and SSD policy; only small norms and four table rows evaluated +test engine::metal::qwen_mtplx::cache::mtplx_kv_cache_graph_matches_reference ... ignored, requires Apple Metal; actual MTPLX KVCache growth, trim and state receipts +test database::tests::session_model_migration_preserves_data_and_accepts_catalog ... ok +test engine::metal::qwen_mtplx::allocator::mtplx_allocator_cache_matches_live_reference_trace ... ok +test engine::metal::qwen_mtplx::cache::mtplx_qsa_cache_graph_matches_reference ... ignored, requires Apple Metal; actual MTPLX positional cache, mirror and held state aliases +test engine::metal::qwen_mtplx::compile_graph::mtplx_compile_scalar_bits_and_custom_kernel_identity ... ignored, requires Metal buffers and external supervisor; scalar ownership only, no GPU dispatch +test engine::kvstore::tests::longest_compatible_prefix_wins_and_hits_survive_reopen ... ok +test engine::metal::qwen_mtplx::decoder::mtplx_decoder_connected_attention_and_moe_advance_caches ... ignored, requires Apple Metal and external test-supervisor; complete decoder/cache smoke +test engine::metal::qwen_mtplx::context_copy::prompt_copy_match_and_backoff_preserve_reference_policy ... ok +test engine::metal::qwen_mtplx::dense::mtplx_dense_attention_batches_match_reference ... ignored, requires Apple M5 Max; actual BF16 batched matmul reference +test engine::metal::qwen_mtplx::dense::mtplx_dense_float_graph_matches_reference ... ignored, requires Apple M5 Max; floating Matmul factory and original dispatch +test engine::metal::qwen_mtplx::dense::mtplx_qsa_score_array_graph_matches_reference ... ignored, requires Apple M5 Max; canonical QSA F32 graph with the original TF32 setting +test engine::metal::qwen_mtplx::encoder::mtplx_empty_bindings_preserve_nil_dependencies_without_dummy_storage ... ignored, requires Apple Metal; empty original-kernel grids and nil bindings +test engine::metal::qwen_mtplx::decoder::mtplx_decoder_residual_preserves_graph_order ... ok +test engine::metal::qwen_mtplx::encoder::mtplx_encoder_commits_after_primitives_and_balances_completion ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::compiled::mtplx_replay_replaces_descriptors_without_rebuilding_primitives ... ok +test engine::metal::qwen_mtplx::encoder::mtplx_encoder_barrier_epochs_and_commit_thresholds ... ok +test engine::metal::qwen_mtplx::encoder::mtplx_encoder_scope_routes_operators_and_allocator ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::encoder::mtplx_encoder_shared_events_and_error_completion_order ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::encoder::mtplx_encoder_untracked_dependencies_across_encoders_and_commands ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::eval::execution_tests::mtplx_eval_drains_failure_preserves_cause_and_retries_only_failed_nodes ... ignored, requires Apple Metal; supervised evaluator failure and retry +test engine::metal::qwen_mtplx::eval::execution_tests::mtplx_eval_releases_intermediates_and_preserves_descriptor_donation ... ignored, requires Apple Metal; graph ownership and last-consumer release +test engine::metal::qwen_mtplx::eval::execution_tests::mtplx_eval_retains_tracer_topology_without_input_donation ... ignored, requires Apple Metal; evaluator tracer donation and retained topology +test engine::metal::qwen_mtplx::eval::execution_tests::mtplx_eval_runs_cpu_gpu_siblings_and_async_continuations ... ignored, requires Apple Metal; tiny supervised CPU/GPU evaluator graph +test engine::metal::qwen_mtplx::eval::mtplx_eval_tape_tracks_stream_fences_per_leaf_and_cross_device_union ... ok +test engine::metal::qwen_mtplx::execution::mtplx_full_installed_ar_matches_reference ... ignored, full installed model; external guard and DS4_QWEN38_AR_REFERENCE required +test engine::metal::qwen_mtplx::eval::mtplx_eval_tape_preserves_bfs_width_and_sibling_choice ... ok +test engine::metal::qwen_mtplx::execution::mtplx_full_installed_mtp_prefill_matches_reference ... ignored, full installed MTP model; external guard and DS4_QWEN38_MTP_PREFILL_REFERENCE required +test engine::metal::qwen_mtplx::eval::mtplx_eval_tape_preserves_scheduled_detach_and_tracer_errors ... ok +test engine::metal::qwen_mtplx::execution::mtplx_full_installed_ping_forward_matches_reference ... ignored, full installed model; requires external memory guard and DS4_QWEN38_FORWARD_REFERENCE +test engine::metal::qwen_mtplx::execution::qwen_power_throttle_drains_before_sleep_and_bypasses_at_full_power ... ignored, requires Apple Metal; run under test-supervisor +test engine::metal::qwen_mtplx::fused::mtplx_compiled_constant_spelling ... ok +test engine::metal::qwen_mtplx::fused::mtplx_compiled_fusion_gpu_matches_reference ... ignored, requires Metal and external supervisor; generated fusion vs pinned SiLU plus multi-output/scalar dispatch +test engine::metal::qwen_mtplx::gdn::mtplx_graph_gdn_forward_matches_model_reference ... ignored, requires Apple Metal; actual MTPLX GDN forward and cache receipts +test engine::metal::qwen_mtplx::gdn::mtplx_graph_gdn_prefill_and_verify_match_model_reference ... ignored, requires Apple Metal; complete GDN Conv1d prefill and fused verify graphs +test engine::metal::qwen_mtplx::gdn::mtplx_graph_gdn_staged_primitives_match_reference ... ignored, requires Apple Metal; 80 actual delta-update receipts and six conv-normalization kernels +test engine::metal::qwen_mtplx::gdn::mtplx_graph_gdn_step_preserves_views_and_siblings ... ignored, requires Apple Metal; GDN custom primitive strided copies and sibling roots +test engine::metal::qwen_mtplx::graph_sync::mtplx_graph_event_snapshots_preserve_values_streams_and_shared_errors ... ignored, requires Apple Metal events, no model weights +test engine::metal::qwen_mtplx::graph_sync::mtplx_graph_fence_coherence_preserves_empty_and_partial_word_spans ... ignored, requires Apple Metal3, model-free empty coherent dispatch +test engine::metal::qwen_mtplx::graph_sync::mtplx_graph_fences_transfer_cpu_gpu_gpu_cpu_in_both_modes ... ignored, requires Apple Metal3, tiny model-free coherent fence kernels +test engine::metal::qwen_mtplx::hyper::mtplx_graph_hyper_forward_matches_reference ... ignored, requires Apple M5 Max; actual MTPLX GatedResidual reference receipts +test engine::metal::qwen_mtplx::hyper::mtplx_graph_hyper_pack_and_reads_match_reference ... ignored, requires Apple Metal; original hyper primitives and runtime pack +test engine::metal::qwen_mtplx::indexing::mtplx_graph_indexing_preserves_layouts_and_offsets ... ignored, requires Apple Metal; Select layout branches, GatherAxis views and empty arguments +test engine::metal::qwen_mtplx::indexing::mtplx_scatter_axis_graph_matches_reference ... ignored, requires Apple Metal; pinned Bool ScatterAxis layout and broadcast paths +test engine::metal::qwen_mtplx::issue103_analysis::issue103_numeric_receipts ... ignored, #103 diagnostic; Metal required, eager versus compiled numeric operands +test engine::metal::qwen_mtplx::issue103_analysis::issue103_sampling_receipts ... ignored, #103 diagnostic; Metal required, pins currently broken sampling law and invalid rows +test engine::metal::qwen_mtplx::issue103_analysis::issue103_sorted_gather_receipts ... ignored, #103 diagnostic; M5 and pinned runtime shader required; initialized sentinels +test engine::metal::qwen_mtplx::issue103_analysis::issue103_tokenizer_receipts ... ignored, #103 diagnostic; installed tokenizer and latest oracle JSONL; no GPU +test engine::metal::qwen_mtplx::issue103_analysis::issue103_vision_position_receipts ... ignored, #103 diagnostic; small active attention module on Metal; mixed position boundary +test engine::metal::qwen_mtplx::issue104::issue104_installed_ar_stream_and_state ... ignored, full pinned Bare-Speed model; external memory guard required +test engine::metal::qwen_mtplx::issue105::issue105_batched_greedy_rejects_each_invalid_row ... ignored, Metal required; run serially under test-supervisor +test engine::metal::qwen_mtplx::issue106::issue106_complete_state_and_checkpoint_recovery ... ignored, installed model and actual pre-fix checkpoint; external memory guard required +test engine::metal::qwen_mtplx::issue106::issue106_upstream_exit_matrix ... ignored, installed model, scripted logits on real caches; external memory guard required +test engine::metal::qwen_mtplx::issue108::issue108_attention_chunk_positions ... ignored, Metal and pinned MTPLX chunk fixtures; external supervisor required +test engine::metal::qwen_mtplx::issue109::issue109_gate_phase_and_dtype_laws ... ignored, Metal and exhaustive pinned gate fixtures; external supervisor required +test engine::metal::qwen_mtplx::issue110::issue110_actual_projection_rows_match_oracle ... ignored, installed layer weights and pinned oracle receipts; external memory supervisor required +test engine::metal::qwen_mtplx::issue110::issue110_guard_allocation_refusal_releases_graph_and_retries ... ignored, Metal allocation refusal and retry; external supervisor required +test engine::metal::qwen_mtplx::issue110::issue110_large_cold_and_warm_prefill ... ignored, full installed model and large partial chunks; serial memory supervisor required +test engine::metal::qwen_mtplx::issue112_bench::issue112_hidden_prefill ... ignored, full installed model hidden-state prefill; serial supervisor required +test engine::metal::qwen_mtplx::issue112_bench::issue112_paired_prefill ... ignored, full installed model; serial memory supervisor and explicit output directory +test engine::metal::qwen_mtplx::linear::mtplx_graph_affine_dequantize_matches_reference ... ignored, requires Apple Metal; original table dequantize dtype/group/bit/layout receipts +test engine::metal::qwen_mtplx::linear::mtplx_graph_affine_quantize_matches_reference ... ignored, requires Apple Metal; original affine quantize dtype/group/bit/layout receipts +test engine::metal::qwen_mtplx::linear::mtplx_graph_gather_qmm_matches_reference ... ignored, requires Apple Metal; original GatherQMM array layouts and expert routing +test engine::metal::qwen_mtplx::linear::mtplx_graph_quantized_linear_batched_identity ... ignored, requires Apple Metal and external supervisor; strided/broadcast batched affine dispatch +test engine::metal::qwen_mtplx::linear::mtplx_graph_quantized_linear_matches_reference ... ignored, requires Apple M5 Max Metal; 78 pinned affine fixtures through lazy graph +test engine::metal::qwen_mtplx::linear::sorted_gather_guard_matches_pinned_call_predicates ... ok +test engine::metal::qwen_mtplx::load::mtplx_safetensors_load_laziness_boundaries_and_lifetime ... ignored, requires Apple Metal; bounded CPU Load and dependent GPU graph +test engine::metal::qwen_mtplx::mlp::mtplx_graph_shared_mlp_matches_reference ... ignored, requires Apple M5 Max; actual MTPLX shared MLP reference receipts +test engine::metal::qwen_mtplx::mlp::mtplx_graph_split_and_compiled_silu_layouts ... ignored, requires Apple Metal; Split sibling ownership and compiled SiLU layouts +test engine::metal::qwen_mtplx::model::mtplx_model_compile_policy_and_run_boundaries ... ok +test engine::metal::qwen_mtplx::moe::mtplx_graph_sparse_moe_matches_reference ... ignored, requires Apple M5 Max; full MoE Array graph, no model weights; use test-supervisor +test engine::metal::qwen_mtplx::moe_sorted::issue112_all_kernel_variants ... ignored, Metal synthetic all supported row-map variants; external supervisor +test engine::metal::qwen_mtplx::moe_sorted::issue112_canary_failures ... ignored, Metal canary rejection and sticky fallback; external supervisor +test engine::metal::qwen_mtplx::moe_sorted::issue112_full_model_state ... ignored, full installed model; validates all cold/warm state receipts against the pre-port oracle +test engine::metal::qwen_mtplx::moe_sorted::issue112_installed_gate_up ... ignored, installed projection weights; external supervisor required +test engine::metal::qwen_mtplx::moe_sorted::row_map_applicability ... ok +test engine::metal::qwen_mtplx::fused::mtplx_compile_fusion_preserves_parent_and_output_boundaries ... ok +test engine::metal::qwen_mtplx::ngram::mtplx_ngram_embedding_matches_reference ... ignored, requires Apple Metal; full resident NGram graph and lazy decode history +test engine::metal::qwen_mtplx::ngram::mtplx_ngram_gpu_rows_and_history_match_reference ... ignored, requires Apple Metal; original lazy NGram GPU row graph, no table +test engine::metal::qwen_mtplx::ngram::mtplx_ngram_resident_matches_reference ... ignored, requires Apple Metal; original NGramTable resident gather/dequantize receipts +test engine::metal::qwen_mtplx::ngram::mtplx_ngram_table_priority ... ignored, requires Apple Metal; NGram table routing and missing-sidecar behavior +test engine::metal::qwen_mtplx::ngram_sidecar::mtplx_ngram_sidecar_matches_reference ... ignored, requires Apple Metal; actual file-backed MTPLX sidecar/LRU/dequantization receipts +test engine::metal::qwen_mtplx::ngram_stage::mtplx_ngram_stage_allclose_branches ... ignored, requires Apple Metal; stage verification special values and original reduction branches +test engine::metal::qwen_mtplx::ngram_stage::mtplx_ngram_stage_matches_reference ... ignored, requires Apple Metal; original complete stage lifecycle and file-backed tables +test engine::metal::qwen_mtplx::normalization::mtplx_graph_norm_modules_match_reference ... ignored, requires Apple Metal; connected Qwen normalization graph golden fixtures +test engine::metal::qwen_mtplx::normalization::mtplx_graph_rms_norm_preserves_reference_layout_and_values ... ignored, requires Apple Metal; graph RMSNorm donation, copy, broadcast and looped kernels +test engine::metal::qwen_mtplx::normalization::mtplx_logsumexp_graph_matches_reference ... ignored, requires Apple Metal; original sampler LogSumExp branches +test engine::metal::qwen_mtplx::normalization::mtplx_softmax_graph_storage_matches_reference ... ignored, requires Apple Metal; original Softmax type and storage branches +test engine::metal::qwen_mtplx::ops::mtplx_concatenate_preserves_general_general_copy_semantics ... ignored, requires Apple Metal; Concatenate promotion, offsets, strided writers and validation +test engine::metal::qwen_mtplx::ops::mtplx_owned_operations_build_and_execute_qsa_layout_graph ... ignored, requires Apple Metal; self-contained lazy QSA-layout recipe, no callback map +test engine::metal::qwen_mtplx::ops::mtplx_slice_and_contiguous_preserve_cache_window_semantics ... ignored, requires Apple Metal; normalized Slice and Contiguous cache-window graph +test engine::metal::qwen_mtplx::owner::issue105_product::issue105_product_ar_cleanup_and_http_retry ... ignored, installed Qwen model; serial test-supervisor with memory guard required +test engine::metal::qwen_mtplx::owner::issue105_product::issue105_product_mtp_cleanup_and_http_retry ... ignored, installed Qwen MTP model; serial test-supervisor with memory guard required +test engine::metal::qwen_mtplx::owner::issue106_product::issue106_product_discards_old_checkpoint_and_recovers_cold ... ignored, installed model and actual pre-fix checkpoint; external memory guard required +test engine::metal::qwen_mtplx::fused::mtplx_compiled_emitter_matches_all_pinned_silu_bodies ... ok +test engine::metal::qwen_mtplx::owner::issue107_product::issue107_vision_product ... ignored, installed model plus vision tower; serial external memory supervisor required +test engine::metal::qwen_mtplx::owner::issue109_product::issue109_verify_rows_and_warm_checkpoint ... ignored, installed model; serial external memory supervisor required +test engine::metal::qwen_mtplx::owner::issue109_product::issue113_default_verify_and_checkpoint ... ignored, installed model and actual pre-head-chunk checkpoint; serial external memory supervisor +test engine::metal::qwen_mtplx::owner::issue111_product::issue111_token_prefix_cache ... ignored, installed model; serial external memory supervisor required +test engine::metal::qwen_mtplx::owner::issue116_product::issue116_normalized_token_cache ... ignored, installed model; serial external memory supervisor required +test engine::metal::qwen_mtplx::owner::issue117_product::issue117_reasoning_history_cache ... ignored, installed model; serial external memory supervisor required +test engine::metal::qwen_mtplx::owner::qwen_product_generator_chat_matches_reference ... ignored, full installed model through product Generator; external supervisor and chat reference required +test engine::metal::qwen_mtplx::owner::qwen_vision_chat_checkpoint_preserves_image_identity ... ignored, requires installed Qwen, Metal, and DS4_QWEN_VISION_IMAGE; run under test-supervisor +test engine::metal::qwen_mtplx::owner::qwen_mtp_progress_publishes_without_turn_completion_or_double_counting ... ok +test engine::metal::qwen_mtplx::ple::mtplx_graph_ple_connected_matches_reference ... ignored, requires Apple M5 Max; complete original PLELayer with real lookup +test engine::metal::qwen_mtplx::ple::mtplx_graph_ple_projection_and_cache_match_reference ... ignored, requires Apple M5 Max; actual MTPLX PLELayer after embedding lookup +test engine::metal::qwen_mtplx::policy::qwen_server_policy_matches_original_functions ... ignored, requires DS4_QWEN38_POLICY_REFERENCE from pinned MTPLX; CPU-only +test engine::metal::qwen_mtplx::policy::sorted_moe_switch_and_family_rehearsal ... ok +test engine::metal::qwen_mtplx::prefill::mtplx_mtp_prefill_driver_matches_reference ... ignored, requires Apple Metal; original streaming MTP history prefill +test engine::metal::qwen_mtplx::prefill::mtplx_prefill_driver_matches_reference ... ignored, requires Apple Metal; original _prefill control flow and raw cache roots +test engine::metal::qwen_mtplx::qsa_attention::mtplx_qsa_attention_module_matches_reference ... ignored, requires Metal and unchanged whole-Attention receipts; no model download +test engine::metal::qwen_mtplx::qsa_compile::mtplx_compiled_indexer_array_replay_matches_reference ... ignored, requires Metal and original compiled indexer receipts; no model weights +test engine::metal::qwen_mtplx::qsa_core::mtplx_qsa_core_host_matches_reference ... ignored, requires Metal and original host-core receipts; no model weights +test engine::metal::qwen_mtplx::qsa_graph::qsa_cache_donation_requires_descriptor_data_and_allocation_contract ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::qsa_graph::qsa_completion_owns_buffers_until_gpu_finishes ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::qsa_graph::qsa_evaluator_retains_inputs_and_siblings_not_primary_data ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::policy::retired_ar_pipeline_flag_cannot_change_server_or_turn_policy ... ok +test engine::metal::qwen_mtplx::qsa_indexer::issue107::issue107_real_geometry_query_and_block_start_positions ... ignored, Metal and independently verified pinned indexer fixtures; external supervisor required +test engine::metal::qwen_mtplx::qsa_indexer::mtplx_qsa_canonical_indexer_routes_match_reference ... ignored, requires Metal and original ongoing indexer routing receipts; no weights +test engine::metal::qwen_mtplx::qsa_graph::qsa_schedule_preserves_the_selected_sibling ... ok +test engine::metal::qwen_mtplx::qsa_indexer::mtplx_qsa_indexer_compiled_commit_matches_reference ... ignored, requires Metal and original indexer lifecycle receipts; no model weights +test engine::metal::qwen_mtplx::qsa_indexer::mtplx_qsa_indexer_prefix_matches_reference ... ignored, requires Apple Metal, original stateful QSAIndexer receipts, no model weights +test engine::metal::qwen_mtplx::qsa_prefill::mtplx_qsa_prefill_graph_matches_reference ... ignored, requires Metal and original two-stage prefill receipts, no weights +test engine::metal::qwen_mtplx::qsa_prepare::mtplx_qsa_prepare_graph_matches_reference ... ignored, requires Apple Metal; parameterized original QSA preparation and traced positions +test engine::metal::qwen_mtplx::qsa_select::mtplx_qsa_fused_graph_matches_reference ... ignored, requires Apple Metal and original QSA selector receipts; no model weights +test engine::metal::qwen_mtplx::random::mtplx_sampler_prefix_matches_reference ... ignored, requires Apple Metal; original target-prefix sampler and default RNG +test engine::metal::qwen_mtplx::random::mtplx_sampler_resident_matches_reference ... ignored, requires Apple Metal; original MTPLX resident sampling and keyed RNG +test engine::metal::qwen_mtplx::reduce::mtplx_sampler_argmax_matches_reference ... ignored, requires Apple Metal; original MTPLX greedy ArgReduce and storage +test engine::metal::qwen_mtplx::reduce::mtplx_sum_graph_matches_reference ... ignored, requires Apple Metal; original shared sum dispatch and QSA mean +test engine::metal::qwen_mtplx::request::qwen_request_settings_override_backend_policy ... ok +test engine::metal::qwen_mtplx::request::qwen_request_progress_uses_ui_frontiers_and_decode_timer ... ok +test engine::metal::qwen_mtplx::residency::mtplx_residency_matches_reference_lifecycle_cases ... ignored, requires Apple Metal 3 / macOS 15, no model weights +test engine::metal::qwen_mtplx::rope::mtplx_rope_graph_matches_reference ... ignored, requires Apple Metal; actual MTPLX RoPE, static YaRN and multi-axis receipts +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_batched_matches_reference ... ignored, requires Apple Metal; original MTPLX bound and serial batch distributions +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_entry_matches_reference ... ignored, requires Apple Metal; actual MTPLX generator sampling entry +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_fallback_matches_reference ... ignored, requires Apple Metal; original sparse fallback and host dense sampler +test database::tests::chat_and_compaction_history_survive_reopen_and_session_deletion ... ok +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_penalties_match_reference ... ignored, requires Apple Metal; original GPU and CPU raw-logit penalties +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_selection_matches_reference ... ignored, requires Apple Metal; original MTPLX sampler selection, no model weights +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_serial_support_matches_reference ... ignored, requires Apple Metal; actual MTPLX serial candidate route, no weights +test engine::metal::qwen_mtplx::scheduler::mtplx_cpu_dispatch_counts_tenth_and_completes_after_failed_work ... ok +test engine::metal::qwen_mtplx::scheduler::mtplx_cpu_primitive_holds_data_and_temporaries_in_fifo_order ... ignored, requires Apple Metal shared storage, no GPU dispatch or model weights +test engine::metal::qwen_mtplx::compile_graph::mtplx_compile_simplify_preserves_reference_parent_rules ... ok +test engine::metal::qwen_mtplx::scheduler::mtplx_scheduler_cpu_events_propagate_errors_without_consuming_source ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::scheduler::mtplx_scheduler_streams_preserve_fifo_errors_and_shutdown ... ok +test engine::metal::qwen_mtplx::sdpa::mtplx_sdpa_vector_matches_reference ... ignored, requires Apple M5 Max; actual pinned fused SDPA kernels +test engine::metal::qwen_mtplx::session_cache::issue114_snapshot_stage_preserves_zero_bits ... ignored, Metal; exact pinned lazy/deep snapshot zero-sign law +test engine::metal::qwen_mtplx::session_cache::mtplx_full_installed_chat_matches_reference ... ignored, full installed model; external guard and DS4_QWEN38_CHAT_REFERENCE required +test engine::metal::qwen_mtplx::session_cache::mtplx_session_boundary_policy ... ok +test engine::metal::qwen_mtplx::session_cache::mtplx_session_final_state_requires_capture_and_safe_cache ... ok +test engine::metal::qwen_mtplx::session_checkpoint::qwen_checkpoint_roundtrip_and_failed_restore_preserve_live_state ... ignored, model-free Metal checkpoint; external supervisor and DS4_QWEN38_CHECKPOINT_DIR required +test engine::metal::qwen_mtplx::session_codec::mtplx_session_codec_host_import_matches_reference ... ignored, model-free Metal imports; external supervisor and DS4_HOST_IMPORT_REFERENCE required +test engine::metal::qwen_mtplx::session_codec::mtplx_session_codec_matches_original_blobs_and_restore ... ignored, tiny GPU arrays; external supervisor and DS4_SESSION_CODEC_REFERENCE required +test engine::metal::qwen_mtplx::sort::mtplx_sort_graph_matches_reference ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::sparse_attention::mtplx_sparse_attention_graph_matches_reference ... ignored, requires Apple M5; actual MTPLX sparse Decode and TensorOps Prefill consumers +test engine::metal::qwen_mtplx::speculative::mtplx_full_installed_mtp_generation_matches_reference ... ignored, full installed model; external guard and DS4_QWEN38_MTP_GENERATION_REFERENCE required +test engine::metal::profile::tests::profile_reports_locality_and_lru_hits ... ok +test engine::metal::qwen_mtplx::stream::mtplx_stream_routes_queues_events_and_finalizes_all_under_pressure ... ignored, requires Apple Metal, tiny model-free two-queue copy +test engine::metal::qwen_mtplx::stream::mtplx_stream_registry_preserves_thread_defaults_templates_and_clear ... ok +test engine::metal::qwen_mtplx::submission::mtplx_submission_attaches_at_flush_eval_finish_and_drop ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx::unary::mtplx_graph_unary_preserves_reference_layout_and_values ... ignored, requires Apple Metal; graph unary layout, donation and kernel boundaries +test engine::metal::qwen_mtplx::verify_sdpa::issue113_head_chunk_outputs_and_dispatch ... ignored, Metal, pinned head-chunk numerical and dispatch fixtures; serial supervisor +test engine::metal::qwen_mtplx::verify_sdpa::issue113_plan_and_policy_match_pinned_oracle ... ok +test engine::metal::qwen_mtplx::views::mtplx_dtype_conversion_and_views_preserve_values_layout_and_donation ... ignored, requires Apple Metal; original conversion/view kernels and donation in lazy eval +test engine::metal::qwen_mtplx::views::mtplx_dynamic_slice_graph_matches_reference ... ignored, requires Metal and original dynamic slice receipts; no model weights +test engine::metal::qwen_mtplx::views::mtplx_reshape_shares_or_copies_in_the_connected_evaluator ... ignored, requires Apple Metal; original Reshape copy kernels and shared backing +test engine::metal::qwen_mtplx::views::mtplx_views_preserve_aliases_offsets_strides_and_gpu_output ... ignored, requires Apple Metal; connected zero-copy views and offset-aware original copy kernel +test engine::metal::qwen_mtplx::vision::qwen_vision_tower_matches_mtplx_image ... ignored, requires Metal and pinned Qwen vision artifacts/reference values +test engine::metal::qwen_mtplx::weights::mtplx_checkpoint_projection_fusions_match_reference ... ignored, requires Apple Metal; bounded lazy checkpoint Load and fusion +test engine::metal::qwen_mtplx::weights::mtplx_installed_checkpoint_fusion_metadata_matches_reference ... ignored, requires installed DS4_QWEN38_ARTIFACTS; headers only, never evaluate weights +test engine::metal::qwen_mtplx_tests::mtplx_affine_selection_preserves_reference_boundaries ... ok +test engine::metal::qwen_mtplx_tests::mtplx_canonical_hyper_and_gdn_kernels_match_reference ... ignored, requires Apple Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_canonical_source_bodies_preserve_pinned_hashes ... ok +test engine::metal::qwen_mtplx_tests::mtplx_connected_delta_update_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_gather_sort_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_gdn_decode_matches_model_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_gdn_forward_matches_model_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_gdn_staged_matches_model_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_norm_modules_match_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_router_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_sparse_moe_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_connected_switch_glu_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_gate_up_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_cache_lifecycle_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_compiled_backings_and_dynamic_copies_match_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_compiled_cache_routing_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_compiled_cache_window_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_compiled_capacity_matches_reference ... ok +test engine::metal::qwen_mtplx_tests::mtplx_qsa_compiled_indexer_modes_match_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_compiled_scalar_sources_match_reference ... ok +test engine::metal::qwen_mtplx_tests::mtplx_qsa_connected_cache_selection_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_eager_topk_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_f32_scores_match_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_indexer_preparation_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_indexer_routes_match_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_prefill_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_qsa_prefill_producer_dispatch_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx::submission::mtplx_all_native_command_commits_pass_the_submission_hook ... ok +test engine::metal::qwen_mtplx_tests::mtplx_qsa_selector_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_runtime_affine_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_runtime_argsort_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::metal::qwen_mtplx_tests::mtplx_runtime_gather_matches_reference ... ignored, requires Apple M5 Max Metal, no model weights +test engine::deepseek_vision::tests::issue100_ds4_preprocess_dimensions_and_limits ... ok +test engine::metal::tests::batch_raw_span_includes_the_chunk_and_previous_window ... ok +test engine::metal::tests::compression_schedule_tracks_the_deepseek_model_shape ... ok +test engine::metal::tests::decode_feature_policy_keeps_device_generations_and_rollbacks_separate ... ok +test engine::metal::tests::decode_raw_span_tracks_the_logical_sliding_window ... ok +test engine::metal::tests::directional_steering_matches_the_ds4_token_oracle ... ignored, requires the installed Flash GGUF, DS4 steering fixture, and a Metal device +test dev_brain::tests::parser_and_index_rebuild_ignore_links_in_code ... ok +test engine::metal::tests::dspark_confidence_quantizes_q8_input_like_ds4 ... ok +test engine::metal::tests::dspark_matches_original_summary_cycles ... ignored, requires installed 0731/DSpark and DS4SERVER_DSPARK_REFERENCE cycle recording +test engine::metal::tests::dspark_markov_activation_matches_q8_rounding ... ok +test engine::metal::tests::dspark_runs_a_target_owned_greedy_cycle ... ignored, requires the installed 0731 Flash and checkpoint-specific DSpark GGUF fixtures +test engine::metal::tests::dspark_scheduler_matches_the_ds4_default_window ... ok +test engine::metal::tests::dspark_verifier_selects_only_the_committed_row ... ok +test engine::metal::tests::flash_0731_long_context_crosses_indexed_prefill_boundary ... ignored, requires a 0731 Flash GGUF and an Apple M5 device +test engine::metal::tests::flash_0731_m5_decode_performance_gate ... ignored, requires a 0731 Flash GGUF and an Apple M5 device +test engine::metal::tests::flash_0731_resident_and_ssd_streaming_choose_the_same_tokens ... ignored, requires the installed 81 GiB Flash GGUF fixture and a Metal device +test engine::metal::tests::flash_0731_runs_exact_sampled_dspark ... ignored, requires the installed 0731 target and checkpoint-specific DSpark GGUF fixtures +test engine::metal::qwen_mtplx::scheduler::mtplx_scheduler_activity_preserves_reference_pressure_and_wait_rules ... ok +test engine::metal::tests::flash_0731_ssd_streaming_maps_and_seeds_batched_prefill_layers ... ignored, requires the installed 81 GiB Flash GGUF fixture and a Metal device +test engine::metal::tests::half_conversion_handles_normal_and_subnormal_values ... ok +test engine::metal::tests::pro_q4_model_spans_remain_isolated ... ok +test engine::metal::tests::pro_ssd_streaming_full_layer_and_selected_batch_agree ... ignored, requires the installed DeepSeek V4 Pro GGUF and Apple Metal +test engine::metal::tests::resident_multi_session_switching_preserves_each_kv_frontier ... ignored, requires the installed 81 GiB Flash GGUF fixture and a Metal device +test engine::metal::tests::generated_hotlists_match_each_model_shape ... ok +test engine::metal::tests::ssd_streaming_supports_dspark ... ignored, requires the installed 0731 Flash and checkpoint-specific DSpark GGUF fixtures +test engine::metal::tests::pro_resident_admission_includes_weights_context_and_scratch ... ok +test engine::metal::tests::streaming_memory_plan_matches_ds4_graph_formulas ... ok +test engine::metal::vision::tests::glm53_resize_matches_reference_token_grids ... ok +test engine::qwen::tests::pinned_artifact_set_loads_and_renders_goldens ... ignored, requires DS4_QWEN38_ARTIFACTS to point at the pinned 105 GB source +test engine::qwen::tests::pinned_qwen_metadata_matches_product_without_executor_state ... ignored, requires DS4_QWEN38_ARTIFACTS to point at the pinned 105 GB source +test engine::qwen_vision::qwen_image_cache_keys_distinguish_pixels_and_preserve_prior_text ... ok +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_distribution_and_rng_match_reference ... ok +test engine::qwen_vision::qwen_vision_positions_and_resize_contract ... ok +test engine::qwen::tests::metadata_and_safetensors_boundaries_fail_closed ... ok +test engine::sampling_tests::checkpoint_rebuilds_explain_compatibility_and_history_misses ... ok +test engine::sampling_tests::checkpoint_tag_accepts_an_unchanged_prefix_before_reminders ... ok +test engine::sampling_tests::checkpoint_tag_covers_the_canonical_chat_state ... ok +test engine::sampling_tests::checkpoint_headers_identify_the_exact_model ... ok +test engine::sampling_tests::ds4_sampler_real_logits_cpu_benchmark ... ignored, CPU-only; requires DS4_SAMPLER_RECORDING with the recorded GLM logits +test engine::sampling_tests::exact_delta_sampling_accepts_or_corrects_the_draft ... ok +test engine::sampling_tests::generated_text_drains_incomplete_bytes_before_phase_changes_and_finish ... ok +test engine::sampling_tests::generated_text_preserves_thinking_utf8_and_split_stops ... ok +test engine::sampling_tests::metal_executes_real_flash_token ... ignored, requires the 80 GiB Flash checkpoint and Apple Metal +test engine::qwen::tests::frozen_inventory_and_memory_categories_are_exact ... ok +test engine::sampling_tests::qwen_rng_matches_numpy_pcg64 ... ok +test engine::sampling_tests::qwen_sampling_uses_mtplx_global_nucleus_mass_before_top_k ... ok +test engine::sampling_tests::resumed_prefill_crossover_matches_each_ds4_graph ... ok +test engine::sampling_tests::sampling_probabilities_match_ds4_filter_order ... ok +test engine::sampling_tests::split_utf8_token_bytes_are_joined_before_decoding ... ok +test engine::sampling_tests::top_k_and_stream_stops_are_applied_before_output ... ok +test engine::sampling_tests::workspace_instruction_identity_participates_in_append_only_kv_keys ... ok +test engine::sampling_tests::zero_temperature_is_greedy ... ok +test engine::tokenizer::issue111::issue111_ds4_families ... ignored, CPU-only; installed DS4 GGUFs and exact oracle IDs +test engine::tokenizer::issue111::issue111_every_unicode_scalar_split ... ignored, CPU-only; exhaustive pinned Qwen split receipt +test engine::tokenizer::issue111::issue111_qwen_tokens_and_requests ... ignored, CPU-only; installed Qwen tokenizer and pinned exact IDs +test engine::metal::qwen_mtplx::ngram::mtplx_ngram_cpu_rows_and_raw_history_match_reference ... ok +test engine::tokenizer::issue116::issue116_nfc_tokens_and_requests ... ignored, CPU-only; pinned NFC receipts and installed Qwen tokenizer +test engine::tokenizer::issue116::nfc_contract_and_composition ... ok +test engine::tokenizer::issue116::only_affected_qwen_history_changes_checkpoint_tags ... ok +test engine::tokenizer::issue117::direct_history_omits_reasoning_but_retains_the_scaffold ... ok +test engine::tokenizer::issue117::issue117_history_renders_tokens_and_context ... ignored, CPU-only; installed Qwen tokenizer and pinned history receipts +test engine::tokenizer::issue117::only_affected_histories_change_keys ... ok +test engine::tokenizer::issue117::reasoning_only_direct_turn_merges_surrounding_users ... ok +test engine::tokenizer::tests::character_helpers_are_total_at_every_byte_offset ... ok +test engine::tokenizer::tests::ds4_chat_matches_original_session_tokens ... ignored, CPU-only; requires DS4SERVER_CHAT_REFERENCE and its installed GGUF +test engine::tokenizer::tests::issue100_visual_text_preserves_image_boundaries ... ignored, CPU-only; requires the installed 0731 tokenizer shared with Vision-Exp +test engine::tokenizer::tests::qwen_checkpoint_continuation_matches_full_conversation ... ignored, requires the installed Qwen tokenizer, but no GPU or model weights +test engine::tokenizer::tests::qwen_plain_chat_matches_mtplx_server_tokens ... ignored, requires local tokenizer and tools/qwen-chat-reference.py output; no inference +test engine::tokenizer::tests::reasoning_prefixes_match_each_model_family_contract ... ok +test engine::validation::tests::configured_glm53_main_fixture_passes_the_exact_layout ... ok +test engine::validation::tests::configured_glm53_vision_fixture_passes_the_exact_layout ... ok +test engine::tokenizer::issue111::qwen_split_boundaries ... ok +test engine::validation::tests::installed_dspark_fixture_passes_the_target_layout ... ok +test engine::validation::tests::issue100_checkpoint_metadata_is_strict ... ignored, requires the pinned Vision-Exp encoder and DSpark support GGUFs +test engine::validation::tests::mxfp4_is_limited_to_routed_experts ... ok +test engine::metal::qwen_mtplx_tests::mtplx_qsa_prefill_workspace_matches_reference ... ok +test extensions::tests::duplicate_skill_names_and_update_rollback_are_deterministic ... ok +test engine::sampling_tests::probability_ratio_speculation_preserves_the_target_distribution ... ok +test extensions::tests::hook_output_is_bounded_typed_and_event_matched ... ok +test dev_brain::tests::skill_validation_and_default_guide_restore_are_strict ... ok +test extensions::tests::manifest_and_paths_are_validated_without_following_escapes ... ok +test extensions::tests::pinned_ponytail_runs_all_lifecycle_modes ... ignored, downloads the pinned Ponytail reference integration +test engine::metal::qwen_mtplx::array::mtplx_array_releases_sibling_cycles_and_deep_input_graphs ... ok +test extensions::tests::symlink_and_variable_escapes_are_rejected ... ok +test extensions::tests::registry_persists_enable_disable_errors_and_uninstall ... ok +test instructions::tests::changes_removals_resume_and_compaction_are_append_only ... ok +test instructions::tests::discovery_is_global_to_local_deduplicated_and_confined ... ok +test instructions::tests::empty_and_repaired_baselines_do_not_repeat_prompt_content ... ok +test instructions::tests::newly_crossed_scope_is_added_once_and_unavailable_content_is_retained ... ok +test metrics::tests::cache_report_splits_buckets_and_folds_index_files_into_their_checkpoint ... ok +test metrics::tests::decode_timer_excludes_later_work_and_resets_between_requests ... ok +test metrics::tests::prompt_timing_keeps_unmeasured_components_distinct_from_zero ... ok +test metrics::tests::removed_preview_metric_code_stays_decodable ... ok +test metrics::tests::snapshots_track_runtime_and_server_counters ... ok +test model::qwen::qwen_vision_is_an_independent_managed_artifact_set ... ok +test model::qwen::tests::embedded_manifest_is_the_frozen_complete_set ... ok +test model::qwen::tests::verification_rejects_hash_mismatch_and_honors_cancellation ... ok +test model::transfer::tests::cancellation_keeps_the_partial_file_for_the_next_run ... ok +test dev_brain::tests::validation_names_every_drifted_source_and_requires_semantic_refresh ... ok +test model::transfer::tests::catalog_and_checksum_verification_are_explicit ... ok +test model::transfer::tests::issue100_managed_intake ... ignored, requires all three pinned Vision-Exp GGUF artifacts; performs full SHA256 and intake validation +test instructions::tests::frames_escape_control_tags_and_enforce_utf8_byte_limits ... ok +test model::transfer::tests::qwen_set_verifies_reloads_and_removes_as_one_managed_artifact ... ignored, requires DS4_QWEN38_ARTIFACTS to point at the pinned 105 GB source +test model::transfer::tests::issue100_catalog_keeps_vision_and_dspark_checkpoint_specific ... ok +test model::transfer::tests::managed_artifact_inventory_and_delete_include_partial_files ... ok +test model::transfer::tests::verification_reports_bytes_read ... ok +test model_eval::tests::acceleration_receipt_reports_effective_sampling_contract ... ok +test model_eval::tests::acceleration_switch_uses_each_models_native_path ... ok +test model_eval::tests::canary_cross_phase_samples_keep_their_start_phase ... ok +test model::transfer::tests::restart_resumes_at_the_existing_partial_byte ... ok +test model_eval::tests::evaluations_exclude_overlapping_workers_and_release_on_exit ... ok +test model_eval::tests::external_canary_observes_phases_and_finishes_on_eof ... ignored, Metal probe only; DS4SERVER_CANARY_BINARY must point to the current release binary +test model_eval::tests::external_canary_protocol_accepts_only_known_phases ... ok +test model_eval::tests::parses_safe_defaults_and_overrides ... ok +test model_eval::tests::rejects_unbounded_or_invalid_limits ... ok +test model_eval::tests::repeated_prompts_form_one_ordered_conversation ... ok +test model_eval::tests::warmup_timing_separates_loading_without_inventing_missing_durations ... ok +test model_eval::tests::worker_progress_events_advance_the_watchdog ... ok +test native_edit::tests::edit_history_supports_undo_and_redo ... ok +test native_edit::tests::paste_replays_a_complete_command_shortcut ... ok +test native_media::tests::media_player_only_accepts_remote_http_urls ... ok +test native_menu::tests::edit_items_are_custom_menu_events ... ok +test native_menu::tests::user_guide_uses_unmodified_f1 ... ok +test native_menu::tests::view_items_keep_the_selected_pane_checked ... ok +test runtime::tests::dropping_active_generation_cancels_it ... ok +test runtime::tests::operation_failures_use_the_matching_event ... ok +test runtime::tests::operation_tracking_is_consistent ... ok +test runtime::tests::runtime_panic_recovery_accepts_the_next_operation ... ok +test server::request::tests::reasoning_requests_preserve_each_models_exact_modes ... ok +test server::tests::canonical_tool_replay_preserves_argument_types ... ok +test server::tests::connection_slots_enforce_the_limit_and_release_on_drop ... ok +test server::tests::content_arrays_match_ds4_text_projection ... ok +test server::tests::context_limit_errors_use_protocol_standard_fields ... ok +test server::tests::cors_headers_are_opt_in_and_cover_preflight ... ok +test server::tests::dsml_tool_stream_projects_incremental_json_arguments ... ok +test server::tests::generated_dsml_becomes_openai_tool_calls ... ok +test server::tests::generated_tool_parser_accepts_ds4_recovery_syntaxes ... ok +test server::tests::model_metadata_uses_the_requested_alias_and_default_token_limit ... ok +test server::tests::protocol_tool_results_require_live_or_replayed_call_ids ... ok +test server::tests::qwen_http_requests_keep_echoed_reasoning_without_tools ... ok +test dev_brain::tests::source_hash_drift_excludes_page_from_authoritative_queries ... ok +test server::tests::responses_parser_rejects_dropped_state_and_preserves_hosted_calls ... ok +test server::tests::sampled_tool_replay_survives_server_restart ... ok +test server::tests::streaming_errors_match_ds4_server_framing ... ok +test dev_brain::tests::validation_keeps_broken_links_editable_and_reports_provenance_errors ... ok +test server::tests::tool_prompt_matches_ds4_server_text ... ok +test server::tests::tool_stream_projects_ds4_recovery_syntaxes ... ok +test settings::tests::captured_ds4_cli_options_are_uniquely_classified ... ok +test settings::tests::effective_settings_build_one_engine_and_turn_configuration ... ok +test settings::tests::effective_settings_preserve_unset_model_defaults ... ok +test settings::tests::execution_settings_keep_ds4_automatic_values_and_glm_rules ... ok +test settings::tests::kv_cache_gates_follow_the_ds4_store_rules ... ok +test settings::tests::reasoning_selection_is_exact_and_model_specific ... ok +test settings::tests::runtime_settings_preserve_automatic_values_and_cross_field_rules ... ok +test settings::tests::speculative_settings_match_acceleration_defaults_and_dependencies ... ok +test dev_brain::tests::verified_skills_are_indexed_and_injected_on_demand ... ok +test server::tests::streaming_prefill_keeps_the_client_connection_alive ... ok +test dev_brain::tests::source_versions_follow_that_file_instead_of_the_whole_worktree ... ok +test model_eval::tests::cpu_accounting_matches_getrusage_units ... ok +test engine::validation::tests::installed_ds4_fixture_opens_and_renders_a_prompt ... ok +test model::transfer::tests::stalled_download_times_out_and_keeps_the_partial_file ... ok +test server::tests::request_deadline_stops_slow_clients ... ok +test engine::sampling_tests::ds4_sampler_matches_original_reference_tokens_and_rng ... ok +test extensions::tests::direct_hook_runner_isolated_environment_and_handles_failures ... ok +test agent::tests::execution_failures_and_timeouts_have_distinct_codes ... ok +kill: 21799: No such process +test agent::tests::dropping_session_tools_stops_live_jobs_and_removes_output ... ok +test engine::qwen_vision::qwen_image_preprocessing_matches_pillow_mtplx ... ok +test agent::tests::local_file_and_bash_tools_execute_in_the_project ... ok +test extensions::tests::lifecycle_matchers_preserve_declared_event_order ... ok +test engine::metal::qwen_mtplx_tests::mtplx_runtime_shaders_are_pinned ... ok +test extensions::tests::hook_failures_are_bounded_actionable_and_non_shell ... ok +test engine::metal::qwen_mtplx::sampling::mtplx_sampler_mixed_distributions_match_reference ... ok + +test result: ok. 323 passed; 0 failed; 247 ignored; 0 measured; 0 filtered out; finished in 5.53s + + Running unittests tools/qwen38-artifacts.rs (target/debug/deps/qwen38_artifacts-3a34dfcd9c4f3de8) + +running 4 tests +test tests::artifact_names_cannot_escape_the_output_directory ... ok +test tests::affine_sample_digest_uses_packed_values_scales_and_biases ... ok +test tests::safetensors_validation_rejects_duplicate_names_and_invalid_ranges ... ok +test tests::artifact_download_resumes_an_authenticated_partial ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running unittests tools/test-supervisor.rs (target/debug/deps/test_supervisor-978addc9e5e8dbda) + +running 4 tests +test supervisor::tests::direct_worker_arguments_are_not_rust_test_arguments ... ok +test supervisor::tests::split_records_cannot_be_interleaved_with_watchdog_samples ... ok +test supervisor::tests::split_probe_records_do_not_reset_progress ... ok +ready{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +ready +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +{"event":"gpu_canary_sample","ok":true} +progressprogressprogressprogressprogressprogressprogressprogresstest supervisor::tests::watchdog_stops_memory_and_silence_but_not_total_runtime ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.33s diff --git a/docs/issue100-vision/tokenizer.log b/docs/issue100-vision/tokenizer.log new file mode 100644 index 0000000..3658586 --- /dev/null +++ b/docs/issue100-vision/tokenizer.log @@ -0,0 +1,5 @@ + +running 1 test +test engine::tokenizer::tests::issue100_visual_text_preserves_image_boundaries ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 569 filtered out; finished in 0.10s diff --git a/metal/DS4-LICENSE.txt b/metal/DS4-LICENSE.txt new file mode 100644 index 0000000..c5b10d4 --- /dev/null +++ b/metal/DS4-LICENSE.txt @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2026 The ds4.c authors +Copyright (c) 2023-2026 The ggml authors +Copyright (c) 2023 DeepSeek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/metal/deepseek4_vision.metal b/metal/deepseek4_vision.metal new file mode 100644 index 0000000..55a0214 --- /dev/null +++ b/metal/deepseek4_vision.metal @@ -0,0 +1,168 @@ +// Derived from antirez/ds4 0aaea5a238fb41a35106a551e73c8409dfb751ac (MIT). +// Copyright (c) 2026 The ds4.c authors; (c) 2023 DeepSeek. +// See DS4-LICENSE.txt for the complete license. +// DeepSeek V4 Flash Vision-Exp operations not covered by shared BF16 matmuls. + +struct deepseek4_vision_rows_args { + uint width; + uint rows; +}; + +struct deepseek4_vision_qkv_args { + uint rows; + uint grid_width; +}; + +struct deepseek4_vision_align_args { + uint grid_height; + uint grid_width; + uint output_rows; +}; + +static inline float deepseek4_vision_round_bf16(float value) { + uint bits = as_type(value); + if ((bits & 0x7f800000u) == 0x7f800000u) return value; + bits += 0x00007fffu + ((bits >> 16u) & 1u); + return as_type(bits & 0xffff0000u); +} + +static inline float deepseek4_vision_erf(float x) { + const float sign = x < 0.0f ? -1.0f : 1.0f; + const float a = abs(x); + const float t = 1.0f / (1.0f + 0.3275911f * a); + const float p = (((((1.061405429f * t - 1.453152027f) * t) + + 1.421413741f) * t - 0.284496736f) * t + + 0.254829592f) * t; + return sign * (1.0f - p * exp(-a * a)); +} + +kernel void kernel_deepseek4_vision_qkv_rope( + constant deepseek4_vision_qkv_args &args, + device const float *qkv, + device const ushort *bias, + device float *q, + device float *k, + device float *v, + uint2 group [[threadgroup_position_in_grid]], + ushort lane [[thread_index_in_simdgroup]]) { + const uint row = group.x; + const uint head = group.y; + if (row >= args.rows || head >= 16u) return; + const ulong qkv_base = (ulong)row * 3072u + (ulong)head * 64u; + const ulong out_base = (ulong)row * 1024u + (ulong)head * 64u; + const float q0 = deepseek4_vision_round_bf16( + qkv[qkv_base + lane] + + glm53_bf16_to_f32(bias[(ulong)head * 64u + lane])); + const float q1 = deepseek4_vision_round_bf16( + qkv[qkv_base + lane + 32u] + + glm53_bf16_to_f32(bias[(ulong)head * 64u + lane + 32u])); + const float k0 = deepseek4_vision_round_bf16( + qkv[qkv_base + 1024u + lane] + + glm53_bf16_to_f32(bias[1024u + (ulong)head * 64u + lane])); + const float k1 = deepseek4_vision_round_bf16( + qkv[qkv_base + 1024u + lane + 32u] + + glm53_bf16_to_f32(bias[1024u + (ulong)head * 64u + lane + 32u])); + const uint y = row / args.grid_width; + const uint x = row - y * args.grid_width; + const uint pos = lane < 16u ? y : x; + const uint freq_index = lane & 15u; + const float inv_freq = powr(10000.0f, -(float)freq_index / 16.0f); + const float angle = (float)pos * inv_freq; + const float cs = cos(angle); + const float sn = sin(angle); + q[out_base + lane] = deepseek4_vision_round_bf16(q0 * cs - q1 * sn); + q[out_base + lane + 32u] = + deepseek4_vision_round_bf16(q1 * cs + q0 * sn); + k[out_base + lane] = deepseek4_vision_round_bf16(k0 * cs - k1 * sn); + k[out_base + lane + 32u] = + deepseek4_vision_round_bf16(k1 * cs + k0 * sn); + v[out_base + lane] = deepseek4_vision_round_bf16( + qkv[qkv_base + 2048u + lane] + + glm53_bf16_to_f32(bias[2048u + (ulong)head * 64u + lane])); + v[out_base + lane + 32u] = deepseek4_vision_round_bf16( + qkv[qkv_base + 2048u + lane + 32u] + + glm53_bf16_to_f32( + bias[2048u + (ulong)head * 64u + lane + 32u])); +} + +kernel void kernel_deepseek4_vision_round_bf16( + constant deepseek4_vision_rows_args &args, + device float *x, + uint2 gid [[thread_position_in_grid]]) { + if (gid.x >= args.width || gid.y >= args.rows) return; + const ulong off = (ulong)gid.y * args.width + gid.x; + x[off] = deepseek4_vision_round_bf16(x[off]); +} + +kernel void kernel_deepseek4_vision_add_residual( + constant deepseek4_vision_rows_args &args, + device float *x, + device const float *residual, + uint2 gid [[thread_position_in_grid]]) { + if (gid.x >= args.width || gid.y >= args.rows) return; + const ulong off = (ulong)gid.y * args.width + gid.x; + x[off] = deepseek4_vision_round_bf16(x[off] + residual[off]); +} + +static inline void deepseek4_vision_swiglu_split( + constant deepseek4_vision_rows_args &args, + device const float *gate_up, + device float *out, + uint2 gid, + bool round_silu) { + if (gid.x >= args.width || gid.y >= args.rows) return; + const ulong source = (ulong)gid.y * args.width * 2u + gid.x; + const float gate = gate_up[source]; + const float up = gate_up[source + args.width]; + float activated = gate / (1.0f + exp(-gate)); + if (round_silu) activated = deepseek4_vision_round_bf16(activated); + out[(ulong)gid.y * args.width + gid.x] = deepseek4_vision_round_bf16(activated * up); +} + +kernel void kernel_deepseek4_vision_swiglu_split( + constant deepseek4_vision_rows_args &args, + device const float *gate_up, + device float *out, + uint2 gid [[thread_position_in_grid]]) { + deepseek4_vision_swiglu_split(args, gate_up, out, gid, false); +} + +/* Match F.unfold(x, 3, stride=3): channels are outermost, followed by the + * row-major 3x3 neighborhood. Values outside the ViT grid are zero. */ +kernel void kernel_deepseek4_vision_aligner_reorder( + constant deepseek4_vision_align_args &args, + device const float *x, + device float *out, + uint2 gid [[thread_position_in_grid]]) { + const uint d = gid.x; + const uint row = gid.y; + if (d >= 9216u || row >= args.output_rows) return; + const uint output_width = (args.grid_width + 2u) / 3u; + const uint block_y = row / output_width; + const uint block_x = row - block_y * output_width; + const uint channel = d / 9u; + const uint within = d - channel * 9u; + const uint source_y = block_y * 3u + within / 3u; + const uint source_x = block_x * 3u + within % 3u; + float value = 0.0f; + if (source_y < args.grid_height && source_x < args.grid_width) { + const ulong source_row = (ulong)source_y * args.grid_width + source_x; + value = x[source_row * 1024u + channel]; + } + out[(ulong)row * 9216u + d] = value; +} + +kernel void kernel_deepseek4_vision_gelu_bias( + constant deepseek4_vision_rows_args &args, + device const float *x, + device const ushort *bias, + device float *out, + uint2 gid [[thread_position_in_grid]]) { + if (gid.x >= args.width || gid.y >= args.rows) return; + const ulong off = (ulong)gid.y * args.width + gid.x; + const float value = deepseek4_vision_round_bf16( + x[off] + glm53_bf16_to_f32(bias[gid.x])); + out[off] = deepseek4_vision_round_bf16( + 0.5f * value * + (1.0f + deepseek4_vision_erf(value * 0.7071067811865475f))); +} diff --git a/metal/dsv4_misc.metal b/metal/dsv4_misc.metal index 567b4e0..7359177 100644 --- a/metal/dsv4_misc.metal +++ b/metal/dsv4_misc.metal @@ -6894,3 +6894,65 @@ kernel void kernel_dsv4_softmax_pool_ratio4_direct( dst[ic * args.head_dim + id] = acc/sum; } + +// Vision-Exp routing from DS4 0aaea5a238fb41a35106a551e73c8409dfb751ac. +struct ds4_metal_args_dsv4_router_select_visual { + uint32_t hash_rows; + uint32_t vocab_size; + uint32_t n_tokens; + uint32_t has_bias; + uint32_t hash_mode; +}; +/* Vision-Exp uses score-based visual routing even in the first three layers, + * where ordinary text rows use a token-id hash table. One threadgroup owns a + * row so the image/text decision is uniform across all barriers. */ +kernel void kernel_dsv4_router_select_visual_batch( + constant ds4_metal_args_dsv4_router_select_visual & args, + device const float *probs, + device const float *bias, + device const float *visual_bias, + device const int32_t *hash, + device const int32_t *tokens, + device int32_t *selected, + threadgroup float *scratch [[threadgroup(0)]], + uint tid [[thread_position_in_threadgroup]], + uint row [[threadgroup_position_in_grid]]) { + if (tid >= 256u || row >= args.n_tokens) return; + + const int32_t token = tokens[row]; + const bool image = token >= 0 && (uint32_t)token >= args.vocab_size; + device int32_t *out = selected + (uint64_t)row * 6u; + if (args.hash_mode && !image) { + const uint hash_row = token >= 0 && (uint32_t)token < args.hash_rows + ? (uint32_t)token : 0u; + if (tid < 6u) out[tid] = hash[(uint64_t)hash_row * 6u + tid]; + return; + } + + threadgroup float *scores = scratch; + threadgroup int32_t *indices = (threadgroup int32_t *)(scratch + 256u); + const float p = probs[(uint64_t)row * 256u + tid]; + scores[tid] = p + (image ? visual_bias[tid] + : (args.has_bias ? bias[tid] : 0.0f)); + indices[tid] = (int32_t)tid; + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (uint k = 2u; k <= 256u; k <<= 1u) { + for (uint j = k >> 1u; j > 0u; j >>= 1u) { + const uint other = tid ^ j; + if (other > tid) { + const bool descending = (tid & k) == 0u; + const int32_t a = indices[tid]; + const int32_t b = indices[other]; + const float sa = scores[(uint)a]; + const float sb = scores[(uint)b]; + if ((descending && sa < sb) || (!descending && sa > sb)) { + indices[tid] = b; + indices[other] = a; + } + } + threadgroup_barrier(mem_flags::mem_threadgroup); + } + } + if (tid < 6u) out[tid] = indices[tid]; +} diff --git a/migrations/20261004000000_allow_deepseek_vision_session_model/down.sql b/migrations/20261004000000_allow_deepseek_vision_session_model/down.sql new file mode 100644 index 0000000..bb21864 --- /dev/null +++ b/migrations/20261004000000_allow_deepseek_vision_session_model/down.sql @@ -0,0 +1,14 @@ +ALTER TABLE sessions ADD COLUMN model_without_deepseek_vision TEXT + CHECK (model_without_deepseek_vision IS NULL OR model_without_deepseek_vision IN ( + 'deepseek-v4-flash-0731', + 'deepseek-v4-pro', + 'glm-5.2', + 'glm-5.3-flash', + 'qwen3.8-flash-next' + )); +UPDATE sessions SET model_without_deepseek_vision = CASE + WHEN model = 'deepseek-v4-flash-vision-exp' THEN NULL + ELSE model +END; +ALTER TABLE sessions DROP COLUMN model; +ALTER TABLE sessions RENAME COLUMN model_without_deepseek_vision TO model; diff --git a/migrations/20261004000000_allow_deepseek_vision_session_model/up.sql b/migrations/20261004000000_allow_deepseek_vision_session_model/up.sql new file mode 100644 index 0000000..b0353d9 --- /dev/null +++ b/migrations/20261004000000_allow_deepseek_vision_session_model/up.sql @@ -0,0 +1,12 @@ +ALTER TABLE sessions ADD COLUMN model_with_deepseek_vision TEXT + CHECK (model_with_deepseek_vision IS NULL OR model_with_deepseek_vision IN ( + 'deepseek-v4-flash-0731', + 'deepseek-v4-flash-vision-exp', + 'deepseek-v4-pro', + 'glm-5.2', + 'glm-5.3-flash', + 'qwen3.8-flash-next' + )); +UPDATE sessions SET model_with_deepseek_vision = model; +ALTER TABLE sessions DROP COLUMN model; +ALTER TABLE sessions RENAME COLUMN model_with_deepseek_vision TO model; diff --git a/native/metal/LICENSE b/native/metal/LICENSE index 5973a4c..c5b10d4 100644 --- a/native/metal/LICENSE +++ b/native/metal/LICENSE @@ -2,6 +2,7 @@ MIT License Copyright (c) 2026 The ds4.c authors Copyright (c) 2023-2026 The ggml authors +Copyright (c) 2023 DeepSeek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/native/metal/README.md b/native/metal/README.md index 643bd0d..c9af090 100644 --- a/native/metal/README.md +++ b/native/metal/README.md @@ -12,3 +12,10 @@ interfaces and are not copies of DS4 application code. DS4Server builds and bundles this local snapshot; it does not read a sibling DS4 checkout. Rust owns the model, graph, session, sampling, and lifecycle. Objective-C remains only at the platform Metal boundary. + + +The Vision Experimental primitives added for issue #100 were reviewed against +DS4 `0aaea5a238fb41a35106a551e73c8409dfb751ac`. The new Metal file and visual +routing kernel follow that reference; image preprocessing, encoder graph, +visual prompt masks and session identities remain in Rust. See +`docs/issue100-vision/README.md` for the source and validation receipts. diff --git a/native/metal/ds4_metal.m b/native/metal/ds4_metal.m index a421ec2..cdec36a 100644 --- a/native/metal/ds4_metal.m +++ b/native/metal/ds4_metal.m @@ -4551,6 +4551,7 @@ static NSString *ds4_gpu_full_source(void) { @[@"DS4_METAL_DENSE_SOURCE", @"metal/dense.metal"], @[@"DS4_METAL_GLM53_BF16_SOURCE", @"metal/glm53_bf16.metal"], @[@"DS4_METAL_GLM53_VISION_SOURCE", @"metal/glm53_vision.metal"], + @[@"DS4_METAL_DEEPSEEK4_VISION_SOURCE", @"metal/deepseek4_vision.metal"], @[@"DS4_METAL_GLM53_KDA_SOURCE", @"metal/glm53_kda.metal"], @[@"DS4_METAL_MOE_SOURCE", @"metal/moe.metal"], @[@"DS4_METAL_DSV4_HC_SOURCE", @"metal/dsv4_hc.metal"], @@ -28972,7 +28973,8 @@ static int ds4_gpu_encode_flash_attention_decode_raw_batch_heads( uint32_t window, uint32_t n_head, uint32_t head_dim, - bool noncausal) { + bool noncausal, + const uint16_t *visual_mask) { if (head_dim != 512 || n_head == 0 || n_tokens == 0 || n_raw == 0 || raw_cap < n_raw || raw_start >= raw_cap) { return 0; @@ -29037,7 +29039,9 @@ static int ds4_gpu_encode_flash_attention_decode_raw_batch_heads( return 0; } - if (noncausal) { + if (visual_mask) { + memcpy([mask_buffer contents], visual_mask, mask_bytes); + } else if (noncausal) { memset([mask_buffer contents], 0, mask_bytes); } else { ds4_gpu_fill_raw_decode_batch_mask((uint16_t *)[mask_buffer contents], @@ -29192,7 +29196,8 @@ static int ds4_gpu_encode_flash_attention_decode_mixed_batch_heads( uint32_t window, uint32_t ratio, uint32_t n_head, - uint32_t head_dim) { + uint32_t head_dim, + const uint16_t *visual_mask) { if (n_comp == 0) { return ds4_gpu_encode_flash_attention_decode_raw_batch_heads(cb, heads, @@ -29208,7 +29213,7 @@ static int ds4_gpu_encode_flash_attention_decode_mixed_batch_heads( window, n_head, head_dim, - false); + false, visual_mask); } if (head_dim != 512 || n_head == 0 || n_tokens == 0 || n_raw == 0 || raw_cap < n_raw || raw_start >= raw_cap || @@ -29290,6 +29295,9 @@ static int ds4_gpu_encode_flash_attention_decode_mixed_batch_heads( return 0; } + if (visual_mask) { + memcpy([mask_buffer contents], visual_mask, mask_bytes); + } else { ds4_gpu_fill_mixed_decode_batch_mask((uint16_t *)[mask_buffer contents], n_tokens, n_raw, @@ -29297,6 +29305,7 @@ static int ds4_gpu_encode_flash_attention_decode_mixed_batch_heads( pos0, window, ratio); + } if (use_comp_mask) { if (!ds4_gpu_encode_cpy_f32_f16_2d(cb, maskbuf, @@ -29618,7 +29627,7 @@ int ds4_gpu_attention_decode_raw_batch_heads_tensor( window, n_head, head_dim, - false)) { + false, NULL)) { return 0; } @@ -29680,7 +29689,7 @@ int ds4_gpu_attention_noncausal_raw_batch_heads_tensor( 0, n_head, head_dim, - true)) { + true, NULL)) { return 0; } @@ -29755,7 +29764,7 @@ int ds4_gpu_attention_decode_mixed_batch_heads_tensor( window, ratio, n_head, - head_dim)) { + head_dim, NULL)) { return 0; } @@ -33034,7 +33043,8 @@ static int ds4_gpu_encode_router_select( uint32_t n_expert_used, float expert_weight_scale, bool has_bias, - bool hash_mode) { + bool hash_mode, + const ds4_gpu_tensor *visual_bias, uint32_t vocab_size) { id selectedbuf = ds4_gpu_tensor_buffer(selected); id weightsbuf = ds4_gpu_tensor_buffer(weights); id probsbuf = ds4_gpu_tensor_buffer(probs); @@ -33052,7 +33062,7 @@ static int ds4_gpu_encode_router_select( fabsf(expert_weight_scale - 1.5f) <= 1.0e-6f; int ok = 0; - if (flash_router_fast_path && + if (!visual_bias && flash_router_fast_path && !g_quality_mode && n_tokens == 1 && getenv("DS4_METAL_DISABLE_ROUTER_SELECT_FUSION") == NULL) { const bool pre_m5_device = @@ -33211,7 +33221,7 @@ static int ds4_gpu_encode_router_select( return 1; } - if (flash_router_fast_path && !g_quality_mode && n_tokens == 1) { + if (!visual_bias && flash_router_fast_path && !g_quality_mode && n_tokens == 1) { id softplus_sqrt_pipeline = ds4_gpu_hot_pipeline(g_dsv4_softplus_sqrt_pipeline, "kernel_dsv4_softplus_sqrt_f32_4"); @@ -33253,7 +33263,28 @@ static int ds4_gpu_encode_router_select( } if (!ok) return 0; - if (hash_mode) { + if (visual_bias) { + id pipeline = ds4_gpu_get_pipeline("kernel_dsv4_router_select_visual_batch"); + if (!pipeline || !tokensbuf || n_expert != 256 || n_expert_used != 6 || + ds4_gpu_tensor_bytes(visual_bias) < 256u * sizeof(float)) return 0; + const uint32_t args[5] = { hash_rows, vocab_size, n_tokens, has_bias, hash_mode }; + const uint32_t zero = 0; + id enc = ds4_gpu_compute_encoder(cb); + [enc setComputePipelineState:pipeline]; + [enc setBytes:args length:sizeof(args) atIndex:0]; + [enc setBuffer:probsbuf offset:probs_off atIndex:1]; + if (has_bias) [enc setBuffer:biasbuf offset:bias_off atIndex:2]; + else [enc setBytes:&zero length:sizeof(zero) atIndex:2]; + [enc setBuffer:ds4_gpu_tensor_buffer(visual_bias) offset:ds4_gpu_tensor_offset(visual_bias) atIndex:3]; + if (hash_mode) [enc setBuffer:hashbuf offset:hash_off atIndex:4]; + else [enc setBytes:&zero length:sizeof(zero) atIndex:4]; + [enc setBuffer:tokensbuf offset:tokens_off atIndex:5]; + [enc setBuffer:selectedbuf offset:selected_off atIndex:6]; + [enc setThreadgroupMemoryLength:2048 atIndex:0]; + [enc dispatchThreadgroups:MTLSizeMake(n_tokens,1,1) threadsPerThreadgroup:MTLSizeMake(256,1,1)]; + ds4_gpu_end_compute_encoder(cb,enc); + ok = 1; + } else if (hash_mode) { ok = ds4_gpu_encode_get_rows_i32_token_rows(cb, hashbuf, hash_off, @@ -33323,7 +33354,7 @@ static int ds4_gpu_encode_router_select( return 1; } - if (flash_router_fast_path && !g_quality_mode && n_tokens == 1) { + if (!visual_bias && flash_router_fast_path && !g_quality_mode && n_tokens == 1) { id router_weights_pipeline = ds4_gpu_hot_pipeline(g_dsv4_router_weights_one_pipeline, "kernel_dsv4_router_weights_one"); @@ -39168,7 +39199,7 @@ int ds4_gpu_router_select_tensor( n_expert_used, expert_weight_scale, has_bias && !hash_mode, - hash_mode); + hash_mode, NULL, 0); if (!had_batch) { ok = ds4_gpu_end_commands() != 0 && ok; } @@ -39264,7 +39295,104 @@ int ds4_gpu_router_select_batch_tensor( n_expert_used, expert_weight_scale, has_bias && !hash_mode, - hash_mode); + hash_mode, NULL, 0); + if (!had_batch) { + ok = ds4_gpu_end_commands() != 0 && ok; + } + if (!ok) return 0; + } + + return 1; +} + +int ds4_gpu_router_select_visual_batch_tensor( + ds4_gpu_tensor *selected, + ds4_gpu_tensor *weights, + ds4_gpu_tensor *probs, + const void *model_map, + uint64_t model_size, + uint64_t bias_offset, + uint64_t hash_offset, + uint32_t hash_rows, + uint32_t n_expert_groups, + uint32_t n_group_used, + bool has_bias, + bool hash_mode, + const ds4_gpu_tensor *logits, + const ds4_gpu_tensor *tokens, + uint32_t n_expert, + uint32_t n_expert_used, + float expert_weight_scale, + uint32_t n_tokens, + const ds4_gpu_tensor *visual_bias, uint32_t vocab_size) { + if (!g_initialized && !ds4_gpu_init()) return 0; + if (!selected || !weights || !probs || !logits || !tokens || !model_map || + n_expert == 0 || n_expert_used == 0 || n_tokens == 0) return 0; + if (n_expert_groups > 1u || n_group_used > 0u) { + fprintf(stderr, "ds4: Metal router group gating is not part of this DeepSeek V4 path\n"); + return 0; + } + + @autoreleasepool { + id logitsbuf = ds4_gpu_tensor_buffer(logits); + id selectedbuf = ds4_gpu_tensor_buffer(selected); + id weightsbuf = ds4_gpu_tensor_buffer(weights); + id probsbuf = ds4_gpu_tensor_buffer(probs); + id tokensbuf = ds4_gpu_tensor_buffer(tokens); + if (!logitsbuf || !selectedbuf || !weightsbuf || !probsbuf || !tokensbuf || + ds4_gpu_tensor_bytes(logits) < (uint64_t)n_tokens * n_expert * sizeof(float) || + ds4_gpu_tensor_bytes(selected) < (uint64_t)n_tokens * n_expert_used * sizeof(int) || + ds4_gpu_tensor_bytes(weights) < (uint64_t)n_tokens * n_expert_used * sizeof(float) || + ds4_gpu_tensor_bytes(probs) < (uint64_t)n_tokens * n_expert * sizeof(float) || + ds4_gpu_tensor_bytes(tokens) < (uint64_t)n_tokens * sizeof(int32_t)) { + fprintf(stderr, "ds4: Metal router batch select received undersized buffers\n"); + return 0; + } + + uint64_t bias_inner = 0; + uint64_t hash_inner = 0; + id biasbuf = nil; + id hashbuf = nil; + NSUInteger bias_set_offset = 0; + NSUInteger hash_set_offset = 0; + if (has_bias && !hash_mode) { + const uint64_t bias_bytes = (uint64_t)n_expert * sizeof(float); + biasbuf = ds4_gpu_wrap_model_range(model_map, model_size, bias_offset, bias_bytes, &bias_inner); + if (!biasbuf) return 0; + bias_set_offset = (NSUInteger)bias_inner; + } + if (hash_mode) { + const uint64_t hash_bytes = (uint64_t)hash_rows * n_expert_used * sizeof(int32_t); + hashbuf = ds4_gpu_wrap_model_range(model_map, model_size, hash_offset, hash_bytes, &hash_inner); + if (!hashbuf) return 0; + hash_set_offset = (NSUInteger)hash_inner; + } + + const bool had_batch = g_batch_cb != nil; + if (!had_batch && ds4_gpu_begin_commands() == 0) return 0; + int owned = 0; + id cb = ds4_gpu_command_buffer(&owned); + int ok = cb && + ds4_gpu_encode_router_select(cb, + selected, + weights, + probs, + logitsbuf, + ds4_gpu_tensor_offset(logits), + biasbuf, + bias_set_offset, + hashbuf, + hash_set_offset, + tokensbuf, + ds4_gpu_tensor_offset(tokens), + NULL, + hash_rows, + n_tokens, + n_expert, + n_expert_used, + expert_weight_scale, + has_bias && !hash_mode, + hash_mode, visual_bias, vocab_size); if (!had_batch) { ok = ds4_gpu_end_commands() != 0 && ok; } @@ -45809,3 +45937,71 @@ int ds4_gpu_glm53_kda_prefill( void ds4_gpu_set_glm_mtp_verify_mode(bool enabled) { (void)enabled; } + +// Primitive dispatch only. The DeepSeek image graph and all layouts live in Rust. +int ds4_gpu_deepseek_vision_dispatch( + const char *kernel, const ds4_gpu_metal_binding *bindings, uint32_t count, + const uint32_t grid[3], const uint32_t threads[3], + uint32_t individual_threads, uint32_t shared_bytes) { + if (!kernel || !bindings || count == 0 || count > 31 || !grid || !threads || + !grid[0] || !grid[1] || !grid[2] || !threads[0] || !threads[1] || !threads[2]) return 0; + if (!g_initialized && !ds4_gpu_init()) return 0; + id pipeline = ds4_gpu_get_pipeline(kernel); + if (!pipeline || (uint64_t)threads[0] * threads[1] * threads[2] > pipeline.maxTotalThreadsPerThreadgroup) return 0; + for (uint32_t i = 0; i < count; i++) { + const ds4_gpu_metal_binding *b = &bindings[i]; + if (b->index > 30 || (b->tensor ? b->offset >= ds4_gpu_tensor_bytes(b->tensor) + : !b->bytes || b->size == 0 || b->size > 4096)) return 0; + } + int owned = 0; + id cb = ds4_gpu_command_buffer(&owned); + id enc = cb ? ds4_gpu_compute_encoder(cb) : nil; + if (!enc) return 0; + [enc setComputePipelineState:pipeline]; + for (uint32_t i = 0; i < count; i++) { + const ds4_gpu_metal_binding *b = &bindings[i]; + if (b->tensor) { + [enc setBuffer:ds4_gpu_tensor_buffer(b->tensor) + offset:ds4_gpu_tensor_offset(b->tensor) + b->offset atIndex:b->index]; + } else { + [enc setBytes:b->bytes length:b->size atIndex:b->index]; + } + } + if (shared_bytes) [enc setThreadgroupMemoryLength:shared_bytes atIndex:0]; + if (individual_threads) { + [enc dispatchThreads:MTLSizeMake(grid[0],grid[1],grid[2]) + threadsPerThreadgroup:MTLSizeMake(threads[0],threads[1],threads[2])]; + } else { + [enc dispatchThreadgroups:MTLSizeMake(grid[0],grid[1],grid[2]) + threadsPerThreadgroup:MTLSizeMake(threads[0],threads[1],threads[2])]; + } + ds4_gpu_end_compute_encoder(cb, enc); + return ds4_gpu_finish_command_buffer(cb, owned, "DeepSeek vision dispatch"); +} + +// Rust constructs the model-specific visual mask; this bridge only binds it +// to the same FlashAttention primitive used by ordinary DeepSeek prefill. +int ds4_gpu_attention_visual_batch_tensor( + ds4_gpu_tensor *heads, const void *map, uint64_t size, uint64_t sinks, + const ds4_gpu_tensor *q, const ds4_gpu_tensor *raw, + const ds4_gpu_tensor *comp, const ds4_gpu_tensor *comp_mask, + uint32_t use_mask, uint32_t rows, uint32_t pos, + uint32_t n_raw, uint32_t raw_cap, uint32_t raw_start, + uint32_t n_comp, uint32_t window, uint32_t ratio, + uint32_t n_head, uint32_t head_dim, + const uint16_t *mask, uint64_t mask_elements) { + if (!map || !heads || !q || !raw || !mask || !rows || !n_raw || + raw_cap < n_raw || raw_start >= raw_cap || (n_comp && (!comp || !ratio)) || + (use_mask && !comp_mask) || mask_elements != (uint64_t)rows * ((uint64_t)n_raw+n_comp) || + sinks > size || (uint64_t)n_head * sizeof(float) > size-sinks) return 0; + if (!g_initialized && !ds4_gpu_init()) return 0; + uint64_t offset = 0; + id weight = ds4_gpu_wrap_model_range(map,size,sinks,(uint64_t)n_head*sizeof(float),&offset); + if (!weight) return 0; + int owned=0; + id cb=ds4_gpu_command_buffer(&owned); + if (!cb || !ds4_gpu_encode_flash_attention_decode_mixed_batch_heads( + cb,heads,weight,offset,q,raw,comp,1,comp_mask,use_mask, + rows,pos,n_raw,raw_cap,raw_start,n_comp,window,ratio,n_head,head_dim,mask)) return 0; + return ds4_gpu_finish_command_buffer(cb,owned,"DeepSeek visual attention"); +} diff --git a/src/config.rs b/src/config.rs index e3c00a4..17403d9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,7 +25,7 @@ impl ModelPreferences { reasoning_mode: model.default_reasoning_mode(), speculative: SpeculativePreferences { glm_mtp: model == ModelChoice::Qwen38FlashNext, - dspark_enabled: model == ModelChoice::DeepSeekV4Flash0731, + dspark_enabled: model.supports_dspark(), ..SpeculativePreferences::default() }, ssd: SsdPreferences::default(), diff --git a/src/database.rs b/src/database.rs index 27d150d..cf504d9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -964,6 +964,14 @@ mod tests { crate::model::ModelChoice::DeepSeekV4Flash0731, ) .unwrap(); + let vision = database + .create_session( + project.id, + "Vision", + PermissionMode::Ai, + crate::model::ModelChoice::DeepSeekV4FlashVision, + ) + .unwrap(); database .connection @@ -973,10 +981,21 @@ mod tests { .connection .run_pending_migrations(MIGRATIONS) .unwrap(); + let sessions = &database.load_projects().unwrap()[0].sessions; assert_eq!( - database.load_projects().unwrap()[0].sessions[0].model(), + sessions + .iter() + .find(|session| session.id == existing.id) + .unwrap() + .model(), existing.model() ); + let downgraded = sessions + .iter() + .find(|session| session.id == vision.id) + .unwrap(); + assert_eq!(downgraded.title, "Vision"); + assert_eq!(downgraded.model(), None); for model in crate::model::MODEL_CHOICES { let session = database diff --git a/src/engine.rs b/src/engine.rs index 8b1f273..3e420c5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,3 +1,4 @@ +mod deepseek_vision; #[cfg(any(target_os = "macos", test))] mod ds4_sampling; mod gguf; @@ -216,6 +217,12 @@ const PRO: Shape = Shape { ..DEEPSEEK_BASE }; +const FLASH_VISION: Shape = Shape { + model: ModelChoice::DeepSeekV4FlashVision, + rms_epsilon: 1e-20, + ..FLASH_0731 +}; + const GLM: Shape = Shape { model: ModelChoice::Glm52, family: ModelFamily::Glm, @@ -619,8 +626,13 @@ impl Model { messages: &[ChatTurn], reasoning: ReasoningMode, ) -> Vec { - self.tokenizer - .encode_conversation(system, messages, reasoning) + if self.shape.model == ModelChoice::DeepSeekV4FlashVision { + self.tokenizer + .encode_deepseek_vision_conversation(system, messages, reasoning) + } else { + self.tokenizer + .encode_conversation(system, messages, reasoning) + } } fn render_history( @@ -1594,13 +1606,63 @@ impl Generator { if encoded_images.next().is_some() { return Err("vision encoder returned too many embeddings".into()); } - let tokens = self - .executor - .model() - .render_conversation(system, &rendered, reasoning); + // DS4 retains the generated token frontier when an authenticated + // conversation appends a turn. Re-tokenizing the assistant's visible + // text can change BPE boundaries and shift historical image blocks. + let continuation = match (&self.executor, messages.split_last()) { + (metal::Executor::DeepSeek(executor), Some((latest, history))) + if executor.model().shape.model == ModelChoice::DeepSeekV4FlashVision + && (latest.user || latest.tool) + && !history.is_empty() + && self.executor.checkpoint_tag() + == conversation_tag(ModelFamily::DeepSeek, system, reasoning, history) => + { + let old_count = messages[..history.len()] + .iter() + .map(|turn| turn.content.matches(VISION_DATA_START).count()) + .sum::(); + executor + .cached_vision_positions(&embeddings[..old_count]) + .map(|positions| { + let mut tokens = self.executor.tokens().to_vec(); + let cursor = tokens.len(); + let latest = rendered.last().expect("latest turn exists"); + let tokenizer = &executor.model().tokenizer; + tokens.extend(if latest.tool { + tokenizer.encode_deepseek_vision_tool_continuation(latest, reasoning) + } else { + tokenizer.encode_deepseek_vision_continuation( + &latest.content, + reasoning, + latest.skip_previous_eos, + ) + }); + (tokens, cursor, positions) + }) + } + _ => None, + }; + let (mut tokens, mut cursor, old_positions) = continuation.unwrap_or_else(|| { + ( + self.executor + .model() + .render_conversation(system, &rendered, reasoning), + 0, + Vec::new(), + ) + }); let mut overlays = Vec::with_capacity(embeddings.len()); - let mut cursor = 0_usize; - for embedding in embeddings { + for (index, mut embedding) in embeddings.into_iter().enumerate() { + if let Some(&(start, count)) = old_positions.get(index) + && let metal::Executor::DeepSeek(executor) = &self.executor + { + let block = executor.layout_vision(&mut embedding, start)?; + if tokens.get(start as usize..(start + count) as usize) != Some(block.as_slice()) { + return Err("cached DeepSeek image token layout is invalid".into()); + } + overlays.push((start, embedding)); + continue; + } let count = embedding.tokens as usize; let relative = tokens[cursor..] .windows(count + 2) @@ -1612,6 +1674,17 @@ impl Generator { .all(|token| *token == VISION_IMAGE_TOKEN) }) .ok_or("rendered prompt lost an image placeholder")?; + if let metal::Executor::DeepSeek(executor) = &self.executor { + let start = cursor + relative; + let position = + u32::try_from(start).map_err(|_| "image prompt position overflow")?; + let block = executor.layout_vision(&mut embedding, position)?; + let block_len = block.len(); + tokens.splice(start..start + count + 2, block); + overlays.push((position, embedding)); + cursor = start + block_len; + continue; + } let start = cursor + relative + 1; overlays.push(( u32::try_from(start).map_err(|_| "image prompt position overflow")?, @@ -1662,8 +1735,15 @@ impl Generator { tokens.len() )); } - let reused = self.executor.align_prompt(&tokens)?; - self.executor.set_vision_overlays(overlays)?; + let reused = if matches!(&self.executor, metal::Executor::DeepSeek(e) if e.model().shape.model == ModelChoice::DeepSeekV4FlashVision) + { + self.executor.set_vision_overlays(overlays)?; + self.executor.align_prompt(&tokens)? + } else { + let reused = self.executor.align_prompt(&tokens)?; + self.executor.set_vision_overlays(overlays)?; + reused + }; self.metrics.kv_prefix_reused(reused); progress(self.executor.position(), self.executor.context(), None); let seed = settings.seed.unwrap_or(0x4453_3453_4552_5645); diff --git a/src/engine/deepseek_vision.rs b/src/engine/deepseek_vision.rs new file mode 100644 index 0000000..31da744 --- /dev/null +++ b/src/engine/deepseek_vision.rs @@ -0,0 +1,454 @@ +//! DeepSeek V4 Flash Vision-Exp image layout and prefill masks. +//! Behavioral reference: DS4 0aaea5a238fb41a35106a551e73c8409dfb751ac. +use image::{DynamicImage, ImageDecoder, ImageReader}; +use sha2::{Digest, Sha256}; +use std::io::Cursor; + +pub(super) const SOURCE_REVISION: &[u8] = b"e46e16bf6035c6f317eb2ac7458eb0362926d402"; +pub(super) const START: i32 = 0; +pub(super) const PAD: i32 = 1; +pub(super) const IMAGE: i32 = 2; +pub(super) const NEWLINE: i32 = 3; +pub(super) const END: i32 = 4; + +pub(super) struct Patches { + pub values: Vec, + pub fingerprint: [u8; 32], + pub width: u32, + pub height: u32, + pub content_width: u32, + pub content_height: u32, + pub grid_width: u32, + pub grid_height: u32, +} + +pub(super) fn decode(encoded: &[u8]) -> Result { + if encoded.is_empty() || encoded.len() > 64 * 1024 * 1024 { + return Err("image is empty or exceeds the 64 MiB encoded limit".into()); + } + let reader = ImageReader::new(Cursor::new(encoded)) + .with_guessed_format() + .map_err(|e| e.to_string())?; + let mut decoder = reader.into_decoder().map_err(|e| e.to_string())?; + let (width, height) = decoder.dimensions(); + if width == 0 + || height == 0 + || width > 16384 + || height > 16384 + || u64::from(width) * u64::from(height) > 64 * 1024 * 1024 + { + return Err("invalid DeepSeek image dimensions".into()); + } + let orientation = decoder.orientation().map_err(|e| e.to_string())?; + let mut image = DynamicImage::from_decoder(decoder).map_err(|e| e.to_string())?; + image.apply_orientation(orientation); + let rgb = image.into_rgb8(); + preprocess(rgb.as_raw(), rgb.width(), rgb.height()) +} + +fn grid_tokens(height: u32, width: u32) -> u32 { + let h = (height / 14).div_ceil(3); + let w = (width / 14).div_ceil(3); + (h + h % 2) * (w + 1) + 2 + (h.div_ceil(2) * (w + 1) % 2) * 2 +} + +fn resize_shape(height: u32, width: u32) -> Result<(u32, u32), String> { + let mut pw = width.min(height * 8); + let mut ph = height; + if u64::from(pw) * u64::from(ph) < 147456 { + let scale = (147456.0 / (f64::from(pw) * f64::from(ph))).sqrt(); + pw = ((f64::from(pw) * scale) as u32).max(1); + ph = ((f64::from(ph) * scale) as u32).max(1); + } + let mut bw = pw.div_ceil(14) * 14; + let mut bh = ph.div_ceil(14) * 14; + let mut budget = 381; + while grid_tokens(bh, bw) > 381 { + if budget <= 4 { + return Err("image cannot fit the DeepSeek vision token budget".into()); + } + let ratio = f64::from(ph) / f64::from(pw); + let mw = ((f64::from(budget) - 2.0) / ratio + 0.25).sqrt() - 0.5; + let mh = mw * ratio; + if mw < 1.0 { + bw = 42; + bh = (((budget - 2) / 2) & !1) * 42; + } else if mh < 2.0 { + bh = 84; + bw = ((budget - 2) / 2 - 1) * 42; + if bw <= 42 { + return Err("image cannot fit the DeepSeek vision token budget".into()); + } + } else { + let mw = mw.floor() as u32; + let mh = mh.floor() as u32 & !1; + let scale = + (f64::from(mw) * 42.0 / f64::from(pw)).min(f64::from(mh) * 42.0 / f64::from(ph)); + bw = (f64::from(pw) * scale / 14.0).floor() as u32 * 14; + bh = (f64::from(ph) * scale / 14.0).floor() as u32 * 14; + } + if bw == 0 || bh == 0 { + return Err("invalid DeepSeek resized image dimensions".into()); + } + budget -= 1; + } + Ok((bh, bw)) +} + +pub(super) fn preprocess(rgb: &[u8], width: u32, height: u32) -> Result { + if width == 0 + || height == 0 + || width > 16384 + || height > 16384 + || u64::from(width) * u64::from(height) > 64 * 1024 * 1024 + || rgb.len() != width as usize * height as usize * 3 + { + return Err("invalid DeepSeek image dimensions".into()); + } + let (bh, bw) = resize_shape(height, width)?; + if bw > 16384 || bh > 16384 || u64::from(bw) * u64::from(bh) > 64 * 1024 * 1024 { + return Err("resized DeepSeek image exceeds the decoded image limits".into()); + } + let (cw, ch, ox, oy) = if width >= height * 8 { + (bw, bh, 0, 0) + } else { + let scale = (f64::from(bw) / f64::from(width)).min(f64::from(bh) / f64::from(height)); + let cw = (f64::from(width) * scale) + .round_ties_even() + .clamp(1.0, f64::from(bw)) as u32; + let ch = (f64::from(height) * scale) + .round_ties_even() + .clamp(1.0, f64::from(bh)) as u32; + ( + cw, + ch, + (f64::from(bw - cw) * 0.5).round_ties_even() as u32, + (f64::from(bh - ch) * 0.5).round_ties_even() as u32, + ) + }; + let mut canvas = vec![127.0_f32; bw as usize * bh as usize * 3]; + let offset = (oy as usize * bw as usize + ox as usize) * 3; + super::metal::vision::resize_bicubic(rgb, width, height, &mut canvas[offset..], cw, ch, bw); + for value in &mut canvas { + *value = *value / 127.5 - 1.0; + } + let gw = bw / 14; + let gh = bh / 14; + let mut values = Vec::with_capacity(gw as usize * gh as usize * 588); + for py in 0..gh { + for px in 0..gw { + for c in 0..3 { + for y in 0..14 { + for x in 0..14 { + values.push( + canvas[((py * 14 + y) as usize * bw as usize + (px * 14 + x) as usize) + * 3 + + c], + ); + } + } + } + } + } + let mut hash = Sha256::new(); + hash.update(width.to_le_bytes()); + hash.update(height.to_le_bytes()); + hash.update(rgb); + Ok(Patches { + fingerprint: hash.finalize().into(), + values, + width, + height, + content_width: cw, + content_height: ch, + grid_width: gw, + grid_height: gh, + }) +} + +pub(super) struct Layout { + pub types: Vec, + pub permutation: Vec, +} + +pub(super) fn layout(height: u32, width: u32, position: u32) -> Result { + if height == 0 || width == 0 { + return Err("invalid DeepSeek image token grid".into()); + } + let rows = u64::from(height) + u64::from(height % 2); + let row_length = u64::from(width) + 1; + let leading = 3 - position % 4; + let trailing = ((rows / 2 * row_length) % 2) * 2; + let count = u64::from(leading) + 2 + rows * row_length + trailing; + if count > 384 { + return Err("DeepSeek image token layout exceeds its model limit".into()); + } + let mut types = vec![PAD; leading as usize]; + let mut permutation = Vec::with_capacity(height as usize * width as usize); + types.push(START); + for pair in 0..rows / 2 { + for column in 0..row_length { + for row in pair * 2..pair * 2 + 2 { + if row < u64::from(height) && column < u64::from(width) { + types.push(IMAGE); + permutation.push((row * u64::from(width) + column) as usize); + } else { + types.push(if row < u64::from(height) { + NEWLINE + } else { + PAD + }); + } + } + } + } + types.extend(std::iter::repeat_n(PAD, trailing as usize)); + types.push(END); + Ok(Layout { types, permutation }) +} + +#[derive(Debug, PartialEq)] +pub(super) struct Span { + pub block: usize, + pub start: usize, + pub end: usize, +} + +pub(super) fn spans(tokens: &[i32], vocab: i32) -> Result, String> { + let mut result = Vec::new(); + let mut cursor = 0; + while cursor < tokens.len() { + if (0..vocab).contains(&tokens[cursor]) { + cursor += 1; + continue; + } + let block = cursor; + while cursor < tokens.len() && tokens[cursor] == vocab + PAD { + cursor += 1; + } + if tokens.get(cursor) != Some(&(vocab + START)) { + return Err("malformed DeepSeek image block".into()); + } + let start = cursor; + cursor += 1; + loop { + let token = *tokens + .get(cursor) + .ok_or("unterminated DeepSeek image block")?; + if token <= vocab + START || token > vocab + END { + return Err("malformed DeepSeek image block".into()); + } + if token == vocab + END { + break; + } + cursor += 1; + } + result.push(Span { + block, + start, + end: cursor, + }); + cursor += 1; + } + Ok(result) +} + +pub(super) fn prefill_chunk(tokens: &[i32], vocab: i32, cap: u32) -> Result { + let mut limit = tokens.len().min(cap as usize); + if limit == 0 { + return Err("empty DeepSeek prefill chunk".into()); + } + for span in spans(tokens, vocab)? { + if span.block >= limit { + break; + } + if span.end + 1 > limit { + if span.end + 1 > cap as usize { + if span.block == 0 { + return Err("DeepSeek image block exceeds prefill capacity".into()); + } + limit = span.block; + break; + } + limit = span.end + 1; + } + } + Ok(limit) +} + +pub(super) fn attention_bounds( + tokens: &[i32], + vocab: i32, + pos: u32, + n_raw: u32, + window: u32, +) -> Result, String> { + let end = pos + .checked_add( + tokens + .len() + .try_into() + .map_err(|_| "vision attention length overflow")?, + ) + .ok_or("vision attention position overflow")?; + if tokens.is_empty() || n_raw == 0 || n_raw > end { + return Err("invalid DeepSeek vision attention extent".into()); + } + let first = end - n_raw; + let mut bounds = Vec::with_capacity(tokens.len() * 2); + for q in 0..tokens.len() as u32 { + let p = pos + q; + bounds.extend([ + first.max(if window == 0 { + 0 + } else { + (p + 1).saturating_sub(window) + }), + p.min(end - 1), + ]); + } + for span in spans(tokens, vocab)? { + for q in span.start..=span.end { + let p = pos + q as u32; + let left = (q - span.start).min(383) as u32; + let right = (span.end - q).min(384) as u32; + let add = if window != 0 && left >= window { + left - (window - 1) + } else { + 0 + }; + bounds[2 * q] = first.max(if window == 0 { + 0 + } else { + p.saturating_sub(window - 1 + add) + }); + bounds[2 * q + 1] = (p + right).min(end - 1); + } + } + Ok(bounds) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn issue100_ds4_image_layout_and_attention_vectors() { + for (h, w, p, types, perm) in [ + ( + 2, + 3, + 0, + vec![1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 3, 3, 4], + vec![0, 3, 1, 4, 2, 5], + ), + ( + 3, + 2, + 5, + vec![1, 1, 0, 2, 2, 2, 2, 3, 3, 2, 1, 2, 1, 3, 1, 4], + vec![0, 2, 1, 3, 4, 5], + ), + (1, 1, 3, vec![0, 2, 1, 3, 1, 4], vec![0]), + ] { + let actual = layout(h, w, p).unwrap(); + assert_eq!(actual.types, types); + assert_eq!(actual.permutation, perm); + } + let tokens = [7, 101, 101, 100, 102, 103, 104, 8, 100, 101, 104, 9]; + assert_eq!( + spans(&tokens, 100).unwrap(), + vec![ + Span { + block: 1, + start: 3, + end: 6 + }, + Span { + block: 8, + start: 8, + end: 10 + } + ] + ); + assert_eq!(prefill_chunk(&tokens, 100, 5).unwrap(), 1); + assert_eq!(prefill_chunk(&tokens[1..], 100, 6).unwrap(), 6); + assert!(prefill_chunk(&tokens[1..], 100, 5).is_err()); + assert!(prefill_chunk(&tokens[4..], 100, 8).is_err()); + for malformed in [ + [101, 4, 5, 6], + [100, 4, 104, 6], + [100, 100, 104, 6], + [100, 102, 6, 7], + [100, 105, 6, 7], + [-1, 100, 104, 6], + ] { + assert!(spans(&malformed, 100).is_err()); + } + assert_eq!( + attention_bounds( + &[7, 101, 100, 102, 102, 102, 102, 102, 104, 8], + 100, + 10, + 20, + 4 + ) + .unwrap(), + vec![ + 7, 10, 8, 11, 9, 18, 10, 18, 11, 18, 12, 18, 12, 18, 12, 18, 12, 18, 16, 19 + ] + ); + } + #[test] + fn issue100_ds4_preprocess_dimensions_and_limits() { + let mut rgb = Vec::new(); + for y in 0..9 { + for x in 0..17 { + rgb.extend([ + (x * 13 + y * 3) as u8, + (x * 5 + y * 17) as u8, + (x * 7 + y * 11) as u8, + ]); + } + } + let p = preprocess(&rgb, 17, 9).unwrap(); + assert_eq!( + ( + p.grid_width, + p.grid_height, + p.content_width, + p.content_height + ), + (38, 20, 529, 280) + ); + assert_eq!(p.values.len(), 760 * 588); + assert!( + p.values + .iter() + .all(|v| v.is_finite() && (-1.0..=1.0).contains(v)) + ); + for (h, w) in [ + (1, 1), + (1, 8), + (1, 16384), + (16384, 1), + (384, 384), + (1024, 1024), + (1080, 1920), + (4096, 4096), + ] { + let (bh, bw) = resize_shape(h, w).unwrap(); + assert!(grid_tokens(bh, bw) <= 381); + for start in 0..4 { + assert!( + layout((bh / 14).div_ceil(3), (bw / 14).div_ceil(3), start) + .unwrap() + .types + .len() + <= 384 + ); + } + } + assert!(preprocess(&[], 0, 1).is_err()); + assert!(layout(384, 384, 0).is_err()); + } +} diff --git a/src/engine/metal.rs b/src/engine/metal.rs index 7865624..ec32b35 100644 --- a/src/engine/metal.rs +++ b/src/engine/metal.rs @@ -1,4 +1,5 @@ mod checkpoint; +mod deepseek_vision; mod glm; mod gpu; mod hotlist; @@ -9,7 +10,7 @@ mod qwen; mod qwen_mtplx_tests; // Whole-turn BF16 backend; never mix its state with legacy FP32 scratch. mod qwen_mtplx; -mod vision; +pub(super) mod vision; pub(super) use vision::VisionEmbedding; use glm::GlmExecutor; @@ -108,7 +109,7 @@ fn environment_present(name: &CStr) -> bool { !unsafe { getenv(name.as_ptr()) }.is_null() } -const SOURCES: [(&str, &str); 25] = [ +const SOURCES: [(&str, &str); 26] = [ ("DS4_METAL_FLASH_ATTN_SOURCE", "flash_attn.metal"), ("DS4_METAL_DENSE_SOURCE", "dense.metal"), ("DS4_METAL_MOE_SOURCE", "moe.metal"), @@ -130,6 +131,10 @@ const SOURCES: [(&str, &str); 25] = [ ("DS4_METAL_SET_ROWS_SOURCE", "set_rows.metal"), ("DS4_METAL_GLM53_BF16_SOURCE", "glm53_bf16.metal"), ("DS4_METAL_GLM53_VISION_SOURCE", "glm53_vision.metal"), + ( + "DS4_METAL_DEEPSEEK4_VISION_SOURCE", + "deepseek4_vision.metal", + ), ("DS4_METAL_GLM53_KDA_SOURCE", "glm53_kda.metal"), ("DS4_METAL_QWEN38_SOURCE", "qwen38.metal"), ("DS4_METAL_MTPLX_QWEN_SOURCE", "mtplx_qwen.metal"), @@ -2409,7 +2414,7 @@ impl SsdPlan { } let mut loaded = 0_u32; let hotlist = match model.shape.model { - ModelChoice::DeepSeekV4Flash0731 => hotlist::FLASH, + ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4FlashVision => hotlist::FLASH, ModelChoice::DeepSeekV4Pro => hotlist::PRO, ModelChoice::Glm52 | ModelChoice::Glm53Flash => { unreachable!("GLM uses its dedicated executor") @@ -2766,7 +2771,7 @@ fn resident_deepseek_admission_bytes( prefill: u32, ) -> Result { resident_deepseek_admission_for_weights( - model.main.len() - model.main.data_offset(), + model.main.len() - model.main.data_offset() + model.vision.as_ref().map_or(0, Gguf::len), model.shape, context, prefill, @@ -3228,6 +3233,10 @@ pub(super) struct ExecutionStats { } pub(super) struct DeepSeekExecutor { + vision: Option, + visual_bias: Vec, + vision_overlays: Vec, + vision_identities: Vec, weights: Weights, session: Session, dspark: Option, @@ -3255,6 +3264,7 @@ pub(super) struct DeepSeekExecutor { } pub(super) struct DeepSeekResidentState { + vision_identities: Vec, session: Session, dspark: Option, logits: Vec, @@ -3314,10 +3324,34 @@ impl DeepSeekExecutor { .map(|spans| (spans.ranges.as_slice(), spans.max_tensor_bytes)); let admission = if let Some(plan) = &ssd_plan { plan.admission_bytes + .checked_add(model.vision.as_ref().map_or(0, Gguf::len)) + .ok_or("DeepSeek runtime memory size overflow")? } else { resident_deepseek_admission_bytes(&model, context, prefill_chunk)? }; - let context_handle = Context::open(&model, quality, ssd.enabled, admission, spans, false)?; + let context_handle = Context::open( + &model, + quality, + ssd.enabled, + admission, + spans, + speculative.keep_vision_loaded, + )?; + let vision = model + .vision + .as_ref() + .map(deepseek_vision::Encoder::bind) + .transpose()?; + let visual_bias = model + .vision + .as_ref() + .map(|v| { + (0..43) + .map(|i| deepseek_vision::buffer(v, &format!("layers.{i}.ffn.gate.bias_vl"))) + .collect::, String>>() + }) + .transpose()? + .unwrap_or_default(); let mxfp4_decode_fast_lookup = mxfp4_decode_fast_lookup_allowed(&model, &weights, quality, ssd.enabled); let steering = Steering::load(&model, steering)?; @@ -3369,6 +3403,10 @@ impl DeepSeekExecutor { model.shape.experts_used, )?; Ok(Self { + vision, + visual_bias, + vision_overlays: Vec::new(), + vision_identities: Vec::new(), weights, session, dspark, @@ -4036,6 +4074,9 @@ impl DeepSeekExecutor { && !self.quality && !tokens.is_empty() && tokens.len() <= streaming_decode_cap + && !tokens + .iter() + .any(|token| *token >= self.model.shape.vocab as i32) { for (index, &token) in tokens.iter().enumerate() { if !progress(self.session.position) { @@ -4045,7 +4086,12 @@ impl DeepSeekExecutor { } return Ok(tokens.len()); } - if tokens.len() < 4 && self.session.position != 0 { + if tokens.len() < 4 + && self.session.position != 0 + && !tokens + .iter() + .any(|token| *token >= self.model.shape.vocab as i32) + { for (index, &token) in tokens.iter().enumerate() { self.eval(token)?; if !progress(self.session.position) { @@ -4066,6 +4112,7 @@ impl DeepSeekExecutor { self.session.context )); } + let resumed = self.session.position != 0; let mut consumed = 0_usize; while consumed < tokens.len() { if !progress(self.session.position) { @@ -4073,14 +4120,29 @@ impl DeepSeekExecutor { } let pos = self.session.position; let mut cap = self.session.prefill_cap; - if pos != 0 { + if pos != 0 && (self.vision.is_none() || resumed) { cap = cap.min(self.session.raw_cap); let offset = pos % self.session.prefill_cap; if offset != 0 { cap = cap.min(self.session.prefill_cap - offset); } } - let rows = (tokens.len() - consumed).min(cap as usize); + let rows = if self.vision.is_some() { + if tokens[consumed] >= self.model.shape.vocab as i32 { + cap = if resumed { + self.session.prefill_cap.min(self.session.raw_cap) + } else { + self.session.prefill_cap + }; + } + crate::engine::deepseek_vision::prefill_chunk( + &tokens[consumed..], + self.model.shape.vocab as i32, + cap, + )? + } else { + (tokens.len() - consumed).min(cap as usize) + }; self.eval_batch(&tokens[consumed..consumed + rows])?; consumed += rows; if !progress(self.session.position) { @@ -4107,12 +4169,29 @@ impl DeepSeekExecutor { if rows == 0 || rows > self.session.prefill_cap { return Err("prefill batch exceeds the configured prefill workspace".into()); } - if tokens - .iter() - .any(|token| *token < 0 || *token as u64 >= self.model.shape.vocab) - { + let visual_tokens = + self.vision.is_some() && tokens.iter().any(|t| *t >= self.model.shape.vocab as i32); + if visual_tokens { + crate::engine::deepseek_vision::spans(tokens, self.model.shape.vocab as i32)?; + } + if tokens.iter().any(|token| { + *token < 0 + || *token as u64 + >= self.model.shape.vocab + if self.vision.is_some() { 5 } else { 0 } + }) { return Err("prefill contains a token outside the vocabulary".into()); } + if visual_tokens + && tokens.iter().enumerate().any(|(row, t)| { + *t >= self.model.shape.vocab as i32 + && !self.vision_overlays.iter().any(|o| { + let pos = self.session.position + row as u32; + pos >= o.identity.start && pos < o.identity.start + o.identity.count + }) + }) + { + return Err("DeepSeek image token has no embedding overlay".into()); + } let prepend_seed = collect_tops && self.session.position > 0 && rows < self.session.prefill_cap @@ -4184,12 +4263,24 @@ impl DeepSeekExecutor { None }; let pipelined_verifier = collect_tops && self.ssd.is_none() && self.profile.is_none(); + let embedding_tokens = if visual_tokens { + let buffer = Buffer::bytes(u64::from(rows) * 4)?; + buffer.write_i32( + &tokens + .iter() + .map(|t| if *t >= shape.vocab as i32 { 0 } else { *t }) + .collect::>(), + )?; + Some(buffer) + } else { + None + }; let mut commands = Some(Commands::begin()?); call( unsafe { ds4_gpu_embed_tokens_hc_tensor( batch.current_hc.raw(), - batch.tokens.raw(), + embedding_tokens.as_ref().unwrap_or(&batch.tokens).raw(), map, size, self.weights.token_embedding.offset, @@ -4201,6 +4292,29 @@ impl DeepSeekExecutor { }, "batch token embedding", )?; + if visual_tokens { + for overlay in &self.vision_overlays { + let start = overlay.identity.start.max(pos); + let end = (overlay.identity.start + overlay.identity.count).min(pos + rows); + if start < end { + call( + unsafe { + ds4_gpu_glm53_scatter_image_hc( + batch.current_hc.raw(), + overlay.embedding.raw(), + start - pos, + start - overlay.identity.start, + end - start, + rows, + shape.embd as u32, + shape.hc as u32, + ) + }, + "DeepSeek image embedding overlay", + )?; + } + } + } if !pipelined_verifier { commands .take() @@ -4261,6 +4375,12 @@ impl DeepSeekExecutor { self.session.raw_cap, self.steering.as_ref(), collect_tops.then_some(prefixes.as_mut_slice()), + visual_tokens.then_some(tokens), + if visual_tokens { + self.visual_bias.get(index) + } else { + None + }, )?; if let Some(profile) = &mut self.profile { profile.record( @@ -4401,6 +4521,14 @@ impl DeepSeekExecutor { }; self.session.position += rows; self.tokens.extend_from_slice(tokens); + if !self.vision_overlays.is_empty() { + self.vision_identities = self + .vision_overlays + .iter() + .filter(|o| o.identity.start + o.identity.count <= self.session.position) + .map(|o| o.identity.clone()) + .collect(); + } if let Some(dspark) = &mut self.dspark { // Keep the captured target rows until DS4's next proposal needs them. dspark.pending_seed = Some(( @@ -4499,6 +4627,8 @@ impl DeepSeekExecutor { dspark.reset_cache(); } self.tokens.clear(); + self.vision_identities.clear(); + self.vision_overlays.clear(); self.checkpoint_tag = [0; 32]; Ok(()) } @@ -4520,6 +4650,7 @@ impl DeepSeekExecutor { _ => None, }; Ok(DeepSeekResidentState { + vision_identities: Vec::new(), session, dspark, logits: vec![0.0; self.model.shape.vocab as usize], @@ -4535,6 +4666,8 @@ impl DeepSeekExecutor { let mut incoming = state .take() .map_or_else(|| self.blank_resident_state(), Ok)?; + std::mem::swap(&mut self.vision_identities, &mut incoming.vision_identities); + self.vision_overlays.clear(); std::mem::swap(&mut self.session, &mut incoming.session); std::mem::swap(&mut self.dspark, &mut incoming.dspark); std::mem::swap(&mut self.logits, &mut incoming.logits); @@ -4549,7 +4682,9 @@ impl DeepSeekExecutor { dspark.begin_request(); } if !tokens.starts_with(&self.tokens) { + let overlays = std::mem::take(&mut self.vision_overlays); self.reset()?; + self.vision_overlays = overlays; } Ok(self.tokens.len()) } @@ -4900,8 +5035,9 @@ impl Executor { ) -> Result, String> { match self { Self::Glm(executor) => executor.encode_visions(encoded), - Self::DeepSeek(_) | Self::Qwen(_) | Self::QwenMtplx(_) => { - Err("vision input requires GLM 5.3 Flash".into()) + Self::DeepSeek(executor) => executor.encode_visions(encoded), + Self::Qwen(_) | Self::QwenMtplx(_) => { + Err("vision input requires a supported vision model".into()) } } } @@ -4912,10 +5048,10 @@ impl Executor { ) -> Result<(), String> { match self { Self::Glm(executor) => executor.set_vision_overlays(overlays), - Self::DeepSeek(_) if overlays.is_empty() => Ok(()), + Self::DeepSeek(executor) => executor.set_vision_overlays(overlays), Self::Qwen(_) | Self::QwenMtplx(_) if overlays.is_empty() => Ok(()), - Self::DeepSeek(_) | Self::Qwen(_) | Self::QwenMtplx(_) => { - Err("vision input requires GLM 5.3 Flash".into()) + Self::Qwen(_) | Self::QwenMtplx(_) => { + Err("vision input requires a supported vision model".into()) } } } @@ -5574,6 +5710,8 @@ fn encode_batch_layer( raw_cap: u32, steering: Option<&Steering>, mut prefixes: Option<&mut [SpecPrefixFrontier]>, + visual_tokens: Option<&[i32]>, + visual_bias: Option<&Buffer>, ) -> Result<(), String> { let hc_dim = shape.hc * shape.embd; let mix_hc = 2 * shape.hc + shape.hc * shape.hc; @@ -5944,7 +6082,21 @@ fn encode_batch_layer( )?; } - if pos == 0 && compressed_rows == 0 { + if let Some(tokens) = visual_tokens { + deepseek_vision::attention( + s, + state, + w, + shape, + map, + size, + tokens, + pos, + raw_cap, + compressed_rows, + ratio, + )?; + } else if pos == 0 && compressed_rows == 0 { call( unsafe { ds4_gpu_attention_prefill_raw_heads_tensor( @@ -6324,31 +6476,61 @@ fn encode_batch_layer( map, size, )?; - call( - unsafe { - ds4_gpu_router_select_batch_tensor( - s.router_selected.raw(), - s.router_weights.raw(), - s.router_probs.raw(), - map, - size, - w.router_bias.map_or(0, |value| value.offset), - w.router_hash.map_or(0, |value| value.offset), - w.router_hash.map_or(0, |value| value.dims[1] as u32), - 0, - 0, - w.router_bias.is_some(), - w.router_hash.is_some(), - s.router_logits.raw(), - s.tokens.raw(), - shape.experts as u32, - shape.experts_used as u32, - shape.expert_weight_scale, - rows, - ) - }, - "batch expert routing", - )?; + if let Some(bias) = visual_bias { + call( + unsafe { + ds4_gpu_router_select_visual_batch_tensor( + s.router_selected.raw(), + s.router_weights.raw(), + s.router_probs.raw(), + map, + size, + w.router_bias.map_or(0, |value| value.offset), + w.router_hash.map_or(0, |value| value.offset), + w.router_hash.map_or(0, |value| value.dims[1] as u32), + 0, + 0, + w.router_bias.is_some(), + w.router_hash.is_some(), + s.router_logits.raw(), + s.tokens.raw(), + shape.experts as u32, + shape.experts_used as u32, + shape.expert_weight_scale, + rows, + bias.raw(), + shape.vocab as u32, + ) + }, + "batch expert routing", + )?; + } else { + call( + unsafe { + ds4_gpu_router_select_batch_tensor( + s.router_selected.raw(), + s.router_weights.raw(), + s.router_probs.raw(), + map, + size, + w.router_bias.map_or(0, |value| value.offset), + w.router_hash.map_or(0, |value| value.offset), + w.router_hash.map_or(0, |value| value.dims[1] as u32), + 0, + 0, + w.router_bias.is_some(), + w.router_hash.is_some(), + s.router_logits.raw(), + s.tokens.raw(), + shape.experts as u32, + shape.experts_used as u32, + shape.expert_weight_scale, + rows, + ) + }, + "batch expert routing", + )?; + } let gate_row = w.expert_gate.bytes / (w.expert_gate.dims[1] * w.expert_gate.dims[2]); let down_row = w.expert_down.bytes / (w.expert_down.dims[1] * w.expert_down.dims[2]); let mut mid_f16 = false; @@ -8141,15 +8323,22 @@ fn update_compressor_stage( fn compression_ratio(shape: super::Shape, layer: u32) -> u32 { match shape.model { - crate::model::ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0, + crate::model::ModelChoice::DeepSeekV4FlashVision + | crate::model::ModelChoice::DeepSeekV4Flash0731 + if layer < 2 => + { + 0 + } crate::model::ModelChoice::DeepSeekV4Pro if layer < 2 => 128, - crate::model::ModelChoice::DeepSeekV4Flash0731 + crate::model::ModelChoice::DeepSeekV4FlashVision + | crate::model::ModelChoice::DeepSeekV4Flash0731 | crate::model::ModelChoice::DeepSeekV4Pro if layer.is_multiple_of(2) => { 4 } - crate::model::ModelChoice::DeepSeekV4Flash0731 + crate::model::ModelChoice::DeepSeekV4FlashVision + | crate::model::ModelChoice::DeepSeekV4Flash0731 | crate::model::ModelChoice::DeepSeekV4Pro => 128, crate::model::ModelChoice::Glm52 | crate::model::ModelChoice::Glm53Flash diff --git a/src/engine/metal/checkpoint.rs b/src/engine/metal/checkpoint.rs index 5da0f2e..b0ca1f9 100644 --- a/src/engine/metal/checkpoint.rs +++ b/src/engine/metal/checkpoint.rs @@ -97,6 +97,15 @@ impl DeepSeekExecutor { for &token in &self.tokens { write_u32(file, token as u32)?; } + if shape.model == ModelChoice::DeepSeekV4FlashVision { + write_u32(file, self.vision_identities.len() as u32)?; + for image in &self.vision_identities { + write_u32(file, image.start)?; + write_u32(file, image.count)?; + file.write_all(&image.fingerprint) + .map_err(|e| e.to_string())?; + } + } for &logit in &self.logits { write_u32(file, logit.to_bits())?; } @@ -212,11 +221,44 @@ impl DeepSeekExecutor { let mut tokens = Vec::with_capacity(token_count as usize); for _ in 0..token_count { let token = read_u32(file)?; - if u64::from(token) >= shape.vocab { + if u64::from(token) + >= shape.vocab + + if shape.model == ModelChoice::DeepSeekV4FlashVision { + 5 + } else { + 0 + } + { return Err("KV checkpoint contains an invalid token".into()); } tokens.push(token as i32); } + let mut vision_identities = Vec::new(); + if shape.model == ModelChoice::DeepSeekV4FlashVision { + let spans = crate::engine::deepseek_vision::spans(&tokens, shape.vocab as i32)?; + let count = read_u32(file)?; + if count as usize != spans.len() || count > 16 { + return Err("KV checkpoint image count is invalid".into()); + } + for span in spans { + let start = read_u32(file)?; + let count = read_u32(file)?; + if start as usize != span.block + || count as usize != span.end + 1 - span.block + || count > 384 + { + return Err("KV checkpoint image layout is invalid".into()); + } + let mut fingerprint = [0; 32]; + file.read_exact(&mut fingerprint) + .map_err(|e| e.to_string())?; + vision_identities.push(deepseek_vision::Identity { + start, + count, + fingerprint, + }); + } + } let mut logits = Vec::with_capacity(shape.vocab as usize); for _ in 0..shape.vocab { logits.push(f32::from_bits(read_u32(file)?)); @@ -299,6 +341,8 @@ impl DeepSeekExecutor { } self.session.position = token_count; self.tokens = tokens; + self.vision_identities = vision_identities; + self.vision_overlays.clear(); self.logits = logits; self.checkpoint_tag = checkpoint_tag; if let Some(dspark) = &mut self.dspark { diff --git a/src/engine/metal/deepseek_vision.rs b/src/engine/metal/deepseek_vision.rs new file mode 100644 index 0000000..34cde1c --- /dev/null +++ b/src/engine/metal/deepseek_vision.rs @@ -0,0 +1,617 @@ +//! Native Rust execution of DS4's Vision-Exp encoder, using the shared BF16 +//! matmul and attention primitives and the checkpoint's exact rounding points. +use super::{Buffer, Commands, Gguf, call, gpu::*}; +use crate::engine::deepseek_vision::{self as image, END, IMAGE, NEWLINE, PAD, START}; +use std::ffi::CStr; + +struct Layer { + norm1: Buffer, + qkv: u64, + qkv_bias: Buffer, + out: u64, + out_bias: Buffer, + norm2: Buffer, + up: u64, + down: u64, +} + +pub(super) struct Encoder { + patch: u64, + patch_bias: Buffer, + norm: Buffer, + align1: u64, + align1_bias: Buffer, + align2: u64, + align2_bias: Buffer, + layers: Vec, + sentinels: [Vec; 5], +} + +pub(super) fn buffer(model: &Gguf, name: &str) -> Result { + let bytes = model.tensor_data(name)?; + let result = Buffer::bytes(bytes.len() as u64)?; + result.write(0, bytes)?; + Ok(result) +} + +#[allow(clippy::too_many_arguments)] +fn dispatch( + kernel: &CStr, + args: &T, + buffers: &[&Buffer], + grid: [u32; 3], + threads: [u32; 3], + individual: bool, + shared: u32, +) -> Result<(), String> { + let mut bindings = vec![MetalBinding { + tensor: std::ptr::null(), + bytes: (args as *const T).cast(), + size: size_of::() as u32, + index: 0, + offset: 0, + }]; + bindings.extend(buffers.iter().enumerate().map(|(i, b)| MetalBinding { + tensor: b.raw(), + bytes: std::ptr::null(), + size: 0, + index: i as u32 + 1, + offset: 0, + })); + call( + unsafe { + ds4_gpu_deepseek_vision_dispatch( + kernel.as_ptr(), + bindings.as_ptr(), + bindings.len() as u32, + grid.as_ptr(), + threads.as_ptr(), + u32::from(individual), + shared, + ) + }, + "DeepSeek vision kernel", + ) +} + +fn rows(kernel: &CStr, width: u32, rows: u32, buffers: &[&Buffer]) -> Result<(), String> { + dispatch( + kernel, + &[width, rows], + buffers, + [width, rows, 1], + [width.min(256), 1, 1], + true, + 0, + ) +} +fn round(x: &Buffer, width: u32, n: u32) -> Result<(), String> { + rows(c"kernel_deepseek4_vision_round_bf16", width, n, &[x]) +} +fn norm(out: &Buffer, x: &Buffer, weight: &Buffer, n: u32) -> Result<(), String> { + dispatch( + c"kernel_glm53_vision_rms_bf16", + &[1024, n, 1e-6_f32.to_bits()], + &[x, weight, out], + [n, 1, 1], + [256, 1, 1], + false, + 32, + )?; + round(out, 1024, n) +} +#[allow(clippy::too_many_arguments)] +fn matmul( + model: &Gguf, + out: &Buffer, + weight: u64, + input: &Buffer, + dim: u32, + width: u32, + n: u32, +) -> Result<(), String> { + call( + unsafe { + ds4_gpu_glm53_matmul_bf16( + out.raw(), + model.map_ptr().cast(), + model.len(), + weight, + dim, + width, + input.raw(), + n, + ) + }, + "DeepSeek vision BF16 projection", + ) +} + +impl Encoder { + pub(super) fn bind(model: &Gguf) -> Result { + let offset = |name: &str| model.tensor(name).map(|t| t.offset); + let mut layers = Vec::with_capacity(32); + for i in 0..32 { + let b = |suffix: &str| buffer(model, &format!("vision.blocks.{i}.{suffix}")); + let o = |suffix: &str| offset(&format!("vision.blocks.{i}.{suffix}")); + layers.push(Layer { + norm1: b("norm1.weight")?, + qkv: o("attn.wqkv.weight")?, + qkv_bias: b("attn.wqkv.bias")?, + out: o("attn.wo.weight")?, + out_bias: b("attn.wo.bias")?, + norm2: b("norm2.weight")?, + up: o("mlp.w1.weight")?, + down: o("mlp.w2.weight")?, + }); + } + let mut sentinels = std::array::from_fn(|_| Vec::new()); + for (i, name) in [ + (START, "image_start"), + (PAD, "image_pad"), + (NEWLINE, "image_newline"), + (END, "image_end"), + ] { + sentinels[i as usize] = model + .tensor_data(name)? + .chunks_exact(2) + .map(|b| f32::from_bits(u32::from(u16::from_le_bytes([b[0], b[1]])) << 16)) + .collect(); + } + Ok(Self { + patch: offset("vision.patch_embed.proj.weight")?, + patch_bias: buffer(model, "vision.patch_embed.proj.bias")?, + norm: buffer(model, "vision.norm.weight")?, + align1: offset("aligner.w1.weight")?, + align1_bias: buffer(model, "aligner.w1.bias")?, + align2: offset("aligner.w2.weight")?, + align2_bias: buffer(model, "aligner.w2.bias")?, + layers, + sentinels, + }) + } + + pub(super) fn encode( + &self, + model: &Gguf, + encoded: &[u8], + ) -> Result { + let p = image::decode(encoded)?; + let n = p.grid_height * p.grid_width; + let aligned = p.grid_height.div_ceil(3) * p.grid_width.div_ceil(3); + let alloc = |width: u64| Buffer::floats(u64::from(n) * width); + let patch = alloc(588)?; + patch.write_f32(&p.values)?; + let mut cur = alloc(1024)?; + let mut tmp = alloc(1024)?; + let qkv = alloc(3072)?; + let q = alloc(1024)?; + let k = alloc(1024)?; + let v = alloc(1024)?; + let attn = alloc(1024)?; + let up = alloc(5632)?; + let mid = alloc(2816)?; + let aligned_input = Buffer::floats(u64::from(aligned) * 9216)?; + let aa = Buffer::floats(u64::from(aligned) * 4096)?; + let ab = Buffer::floats(u64::from(aligned) * 4096)?; + let commands = Commands::begin()?; + matmul(model, &cur, self.patch, &patch, 588, 1024, n)?; + rows( + c"kernel_glm53_vision_add_bias", + 1024, + n, + &[&cur, &self.patch_bias], + )?; + round(&cur, 1024, n)?; + for layer in &self.layers { + norm(&tmp, &cur, &layer.norm1, n)?; + matmul(model, &qkv, layer.qkv, &tmp, 1024, 3072, n)?; + dispatch( + c"kernel_deepseek4_vision_qkv_rope", + &[n, p.grid_width], + &[&qkv, &layer.qkv_bias, &q, &k, &v], + [n, 16, 1], + [32, 1, 1], + false, + 0, + )?; + dispatch( + c"kernel_glm53_vision_attention", + &[n, 0.125_f32.to_bits()], + &[&q, &k, &v, &attn], + [n, 16, 1], + [32, 1, 1], + false, + 0, + )?; + round(&attn, 1024, n)?; + matmul(model, &tmp, layer.out, &attn, 1024, 1024, n)?; + rows( + c"kernel_glm53_vision_bias_residual", + 1024, + n, + &[&tmp, &layer.out_bias, &cur], + )?; + round(&tmp, 1024, n)?; + std::mem::swap(&mut cur, &mut tmp); + norm(&tmp, &cur, &layer.norm2, n)?; + matmul(model, &up, layer.up, &tmp, 1024, 5632, n)?; + round(&up, 5632, n)?; + rows( + c"kernel_deepseek4_vision_swiglu_split", + 2816, + n, + &[&up, &mid], + )?; + matmul(model, &tmp, layer.down, &mid, 2816, 1024, n)?; + round(&tmp, 1024, n)?; + rows( + c"kernel_deepseek4_vision_add_residual", + 1024, + n, + &[&tmp, &cur], + )?; + std::mem::swap(&mut cur, &mut tmp); + } + norm(&tmp, &cur, &self.norm, n)?; + dispatch( + c"kernel_deepseek4_vision_aligner_reorder", + &[p.grid_height, p.grid_width, aligned], + &[&tmp, &aligned_input], + [9216, aligned, 1], + [256, 1, 1], + true, + 0, + )?; + matmul(model, &aa, self.align1, &aligned_input, 9216, 4096, aligned)?; + rows( + c"kernel_deepseek4_vision_gelu_bias", + 4096, + aligned, + &[&aa, &self.align1_bias, &ab], + )?; + matmul(model, &aa, self.align2, &ab, 4096, 4096, aligned)?; + rows( + c"kernel_glm53_vision_add_bias", + 4096, + aligned, + &[&aa, &self.align2_bias], + )?; + round(&aa, 4096, aligned)?; + commands.finish()?; + let mut values = vec![0.0; aligned as usize * 4096]; + aa.read_f32(&mut values)?; + Ok(super::VisionEmbedding { + fingerprint: p.fingerprint, + values, + tokens: aligned, + width: p.width, + height: p.height, + content_width: p.content_width, + content_height: p.content_height, + grid_width: p.grid_width.div_ceil(3), + grid_height: p.grid_height.div_ceil(3), + }) + } + + pub(super) fn layout( + &self, + embedding: &mut super::VisionEmbedding, + position: u32, + vocab: i32, + ) -> Result, String> { + let layout = image::layout(embedding.grid_height, embedding.grid_width, position)?; + if embedding.tokens as usize != layout.permutation.len() + || embedding.values.len() != layout.permutation.len() * 4096 + { + return Err("invalid DeepSeek natural image embedding".into()); + } + let mut values = Vec::with_capacity(layout.types.len() * 4096); + let mut images = layout.permutation.iter(); + for &kind in &layout.types { + if kind == IMAGE { + let row = *images.next().ok_or("invalid DeepSeek image permutation")?; + values.extend_from_slice(&embedding.values[row * 4096..(row + 1) * 4096]); + } else { + values.extend_from_slice(&self.sentinels[kind as usize]); + } + } + embedding.values = values; + embedding.tokens = layout.types.len() as u32; + embedding.grid_height = 0; + embedding.grid_width = 0; + Ok(layout.types.into_iter().map(|t| t + vocab).collect()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct Identity { + pub start: u32, + pub count: u32, + pub fingerprint: [u8; 32], +} +pub(super) struct Overlay { + pub identity: Identity, + pub embedding: Buffer, +} + +impl super::DeepSeekExecutor { + pub(in crate::engine) fn cached_vision_positions( + &self, + images: &[super::VisionEmbedding], + ) -> Option> { + (images.len() == self.vision_identities.len() + && images + .iter() + .zip(&self.vision_identities) + .all(|(image, old)| { + image.fingerprint == old.fingerprint + && image::layout(image.grid_height, image.grid_width, old.start) + .is_ok_and(|layout| layout.types.len() == old.count as usize) + })) + .then(|| { + self.vision_identities + .iter() + .map(|old| (old.start, old.count)) + .collect() + }) + } + + pub(in crate::engine) fn layout_vision( + &self, + embedding: &mut super::VisionEmbedding, + position: u32, + ) -> Result, String> { + self.vision + .as_ref() + .ok_or("DeepSeek vision encoder is not loaded")? + .layout(embedding, position, self.model.shape.vocab as i32) + } + pub(super) fn encode_visions( + &self, + encoded: &[Vec], + ) -> Result, String> { + let encoder = self + .vision + .as_ref() + .ok_or("DeepSeek vision encoder is not loaded")?; + let model = self + .model + .vision + .as_ref() + .ok_or("DeepSeek vision sidecar is not loaded")?; + if !self.speculative.keep_vision_loaded { + call( + unsafe { + ds4_gpu_set_transient_model_map_range( + model.map_ptr().cast(), + model.len(), + model.data_offset(), + model.len() - model.data_offset(), + model.max_tensor_bytes(), + ) + }, + "mapping DeepSeek vision weights", + )?; + } + let result = encoded + .iter() + .map(|bytes| encoder.encode(model, bytes)) + .collect(); + let released = if self.speculative.keep_vision_loaded { + Ok(()) + } else { + call( + unsafe { ds4_gpu_release_transient_model_map(model.map_ptr().cast(), model.len()) }, + "releasing DeepSeek vision weights", + ) + }; + match (result, released) { + (Ok(result), Ok(())) => Ok(result), + (Err(e), _) | (_, Err(e)) => Err(e), + } + } + pub(super) fn set_vision_overlays( + &mut self, + overlays: Vec<(u32, super::VisionEmbedding)>, + ) -> Result<(), String> { + if self.vision.is_none() && !overlays.is_empty() { + return Err("DeepSeek vision encoder is not loaded".into()); + } + let mut next = Vec::with_capacity(overlays.len()); + let mut end = 0; + for (start, image) in overlays { + if image.tokens == 0 + || image.tokens > 384 + || image.values.len() != image.tokens as usize * 4096 + || start < end + { + return Err("invalid DeepSeek image overlay".into()); + } + end = start + .checked_add(image.tokens) + .ok_or("DeepSeek image position overflow")?; + if end > self.session.context { + return Err("DeepSeek image exceeds the session context".into()); + } + let embedding = Buffer::floats(image.values.len() as u64)?; + embedding.write_f32(&image.values)?; + next.push(Overlay { + identity: Identity { + start, + count: image.tokens, + fingerprint: image.fingerprint, + }, + embedding, + }); + } + let matches = self.vision_identities.len() <= next.len() + && self + .vision_identities + .iter() + .zip(&next) + .all(|(old, new)| old == &new.identity) + && next + .get(self.vision_identities.len()) + .is_none_or(|image| image.identity.start >= self.session.position); + if !matches { + self.reset()?; + } + self.vision_overlays = next; + Ok(()) + } +} + +#[allow(clippy::too_many_arguments)] +pub(super) fn attention( + s: &super::BatchScratch, + state: &super::LayerState, + w: &super::Layer, + shape: crate::engine::Shape, + map: *const std::ffi::c_void, + size: u64, + tokens: &[i32], + pos: u32, + raw_cap: u32, + n_comp: u32, + ratio: u32, +) -> Result<(), String> { + let n = tokens.len() as u32; + let (n_raw, raw_start) = if pos == 0 { + (n, 0) + } else { + super::raw_batch_span(pos, n, raw_cap, shape.sliding_window as u32) + }; + let bounds = image::attention_bounds( + tokens, + shape.vocab as i32, + pos, + n_raw, + shape.sliding_window as u32, + )?; + let mut mask = Vec::with_capacity(n as usize * (n_raw + n_comp) as usize); + let first = pos + n - n_raw; + for row in 0..n { + for key in 0..n_raw { + mask.push( + if (bounds[row as usize * 2]..=bounds[row as usize * 2 + 1]) + .contains(&(first + key)) + { + 0 + } else { + 0xfc00 + }, + ); + } + let visible = (pos + row + 1).checked_div(ratio).unwrap_or(0); + mask.extend((0..n_comp).map(|key| if key < visible { 0 } else { 0xfc00 })); + } + let indexed = ratio == 4 && n_comp > shape.indexer_top_k as u32; + let comp_mask = if indexed { + let indexer = state + .indexer + .as_ref() + .ok_or("DeepSeek visual indexer state is missing")?; + let scale = ((shape.indexer_heads * shape.indexer_head_dim) as f32) + .sqrt() + .recip(); + call( + unsafe { + if pos == 0 { + ds4_gpu_indexer_scores_prefill_tensor( + s.indexer_scores.raw(), + s.indexer_q.raw(), + s.indexer_weights.raw(), + indexer.cache.raw(), + n_comp, + n, + shape.indexer_heads as u32, + shape.indexer_head_dim as u32, + ratio, + scale, + ) + } else { + ds4_gpu_indexer_scores_decode_batch_tensor( + s.indexer_scores.raw(), + s.indexer_q.raw(), + s.indexer_weights.raw(), + indexer.cache.raw(), + n_comp, + n, + pos, + shape.indexer_heads as u32, + shape.indexer_head_dim as u32, + ratio, + scale, + ) + } + }, + "DeepSeek visual indexer scores", + )?; + call( + unsafe { + ds4_gpu_indexer_topk_tensor( + s.indexer_selected.raw(), + s.indexer_scores.raw(), + n_comp, + n, + shape.indexer_top_k as u32, + ) + }, + "DeepSeek visual indexer top-k", + )?; + let mask = Buffer::floats(u64::from(n) * u64::from(n_comp))?; + call( + unsafe { + ds4_gpu_dsv4_topk_mask_tensor( + mask.raw(), + s.indexer_selected.raw(), + n_comp, + n, + shape.indexer_top_k as u32, + ) + }, + "DeepSeek visual compressed mask", + )?; + Some(mask) + } else { + None + }; + call( + unsafe { + ds4_gpu_attention_visual_batch_tensor( + s.heads.raw(), + map, + size, + w.attn_sinks.offset, + s.q.raw(), + state.raw_cache.raw(), + state + .compression + .as_ref() + .map_or(std::ptr::null(), |c| c.cache.raw()), + comp_mask + .as_ref() + .map_or(std::ptr::null(), |b| b.raw().cast_const()), + u32::from(indexed), + n, + pos, + n_raw, + raw_cap, + raw_start, + n_comp, + shape.sliding_window as u32, + ratio, + shape.heads as u32, + shape.head_dim as u32, + mask.as_ptr(), + mask.len() as u64, + ) + }, + "DeepSeek visual attention", + ) +} + +#[cfg(test)] +#[path = "issue100.rs"] +mod tests; diff --git a/src/engine/metal/glm.rs b/src/engine/metal/glm.rs index 3c5533e..21a87e0 100644 --- a/src/engine/metal/glm.rs +++ b/src/engine/metal/glm.rs @@ -7196,6 +7196,9 @@ mod tests { let prefill_tps = prompt.len() as f64 / prefill_started.elapsed().as_secs_f64(); let image_logits = executor.logits().to_vec(); let make_embedding = |values| crate::engine::metal::vision::VisionEmbedding { + fingerprint: [0; 32], + grid_width: 0, + grid_height: 0, values, tokens: embedding_shape.0, width: embedding_shape.1, diff --git a/src/engine/metal/gpu.rs b/src/engine/metal/gpu.rs index ebbdff5..994a1a3 100644 --- a/src/engine/metal/gpu.rs +++ b/src/engine/metal/gpu.rs @@ -125,6 +125,48 @@ pub(super) struct GpuBusyStats { } unsafe extern "C" { + pub(super) fn ds4_gpu_dsv4_topk_mask_tensor( + mask: *mut GpuTensor, + topk: *const GpuTensor, + n_comp: u32, + n_tokens: u32, + top_k: u32, + ) -> i32; + + pub(super) fn ds4_gpu_attention_visual_batch_tensor( + heads: *mut GpuTensor, + map: *const c_void, + size: u64, + sinks: u64, + q: *const GpuTensor, + raw: *const GpuTensor, + comp: *const GpuTensor, + comp_mask: *const GpuTensor, + use_mask: u32, + rows: u32, + pos: u32, + n_raw: u32, + raw_cap: u32, + raw_start: u32, + n_comp: u32, + window: u32, + ratio: u32, + n_head: u32, + head_dim: u32, + mask: *const u16, + mask_elements: u64, + ) -> i32; + + pub(super) fn ds4_gpu_deepseek_vision_dispatch( + kernel: *const c_char, + bindings: *const MetalBinding, + count: u32, + grid: *const u32, + threads: *const u32, + individual_threads: u32, + shared_bytes: u32, + ) -> i32; + pub(super) fn ds4_gpu_init() -> i32; pub(super) fn ds4_gpu_cleanup(); pub(super) fn ds4_gpu_metal4_tensor_api_enabled() -> i32; @@ -1703,6 +1745,28 @@ unsafe extern "C" { scale: f32, rows: u32, ) -> i32; + pub(super) fn ds4_gpu_router_select_visual_batch_tensor( + selected: *mut GpuTensor, + weights: *mut GpuTensor, + probs: *mut GpuTensor, + map: *const c_void, + size: u64, + bias: u64, + hash: u64, + hash_rows: u32, + expert_groups: u32, + groups_used: u32, + has_bias: bool, + hash_mode: bool, + logits: *const GpuTensor, + tokens: *const GpuTensor, + experts: u32, + experts_used: u32, + scale: f32, + rows: u32, + visual_bias: *const GpuTensor, + vocab_size: u32, + ) -> i32; pub(super) fn ds4_gpu_routed_moe_batch_tensor( out: *mut GpuTensor, gate: *mut GpuTensor, diff --git a/src/engine/metal/issue100.rs b/src/engine/metal/issue100.rs new file mode 100644 index 0000000..181d709 --- /dev/null +++ b/src/engine/metal/issue100.rs @@ -0,0 +1,585 @@ +use super::*; +#[test] +#[ignore = "requires the pinned Vision-Exp sidecar and Apple Metal"] +fn issue100_encoder_real_weights() { + super::super::configure_sources().unwrap(); + let root = std::env::var_os("DS4_ISSUE100_ARTIFACTS") + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::path::PathBuf::from("local-eval-results/issue100-artifacts")); + let path = root.join("DeepSeek-V4-Flash-Vision-Encoder.gguf"); + crate::engine::validation::validate_vision_artifact(&path).unwrap(); + let model = Gguf::open(&path).unwrap(); + call(unsafe { ds4_gpu_init() }, "initializing vision test").unwrap(); + call( + unsafe { + ds4_gpu_set_model_map_range( + model.map_ptr().cast(), + model.len(), + model.data_offset(), + model.len() - model.data_offset(), + model.max_tensor_bytes(), + ) + }, + "mapping vision test weights", + ) + .unwrap(); + let encoder = Encoder::bind(&model).unwrap(); + let image_path = std::env::var_os("DS4_ISSUE100_IMAGE").expect("DS4_ISSUE100_IMAGE"); + let encoded = std::fs::read(image_path).unwrap(); + let patches = image::decode(&encoded).unwrap(); + let embedding = encoder.encode(&model, &encoded).unwrap(); + assert_eq!(embedding.values.len(), embedding.tokens as usize * 4096); + assert!(embedding.values.iter().all(|x| x.is_finite())); + let prefix = std::env::var_os("DS4_ISSUE100_ENCODER_OUTPUT") + .map(std::path::PathBuf::from) + .expect("DS4_ISSUE100_ENCODER_OUTPUT"); + for (suffix, values) in [ + ("patches", &patches.values), + ("embedding", &embedding.values), + ] { + let bytes = values + .iter() + .flat_map(|v| v.to_le_bytes()) + .collect::>(); + std::fs::write(prefix.with_extension(format!("{suffix}.f32")), bytes).unwrap(); + } + let info = serde_json::json!({"grid":[patches.grid_height,patches.grid_width],"aligned_grid":[embedding.grid_height,embedding.grid_width],"tokens":embedding.tokens,"width":embedding.width,"height":embedding.height,"values":embedding.values.len()}); + std::fs::write( + prefix.with_extension("json"), + serde_json::to_vec_pretty(&info).unwrap(), + ) + .unwrap(); + eprintln!("{info}"); + drop(encoder); + unsafe { ds4_gpu_cleanup() }; +} + +#[test] +#[ignore = "requires Apple Metal; isolated visual routing and attention primitives"] +fn issue100_visual_routing_and_attention() { + use crate::engine::deepseek_vision::attention_bounds; + super::super::configure_sources().unwrap(); + call(unsafe { ds4_gpu_init() }, "initializing visual primitives").unwrap(); + let mut map = memmap2::MmapMut::map_anon(16384).unwrap(); + let hash_offset = 1024; + for expert in 0..256 { + let bias = if (30..36).contains(&expert) { + 100.0 + expert as f32 + } else { + 0.0 + }; + map[expert * 4..expert * 4 + 4].copy_from_slice(&bias.to_le_bytes()); + } + for token in 0..4 { + for expert in 0..6 { + let id = 60 + (token * 6 + expert) as i32; + let offset = hash_offset + (token * 6 + expert) * 4; + map[offset..offset + 4].copy_from_slice(&id.to_le_bytes()); + } + } + call( + unsafe { + ds4_gpu_set_model_map_range( + map.as_ptr().cast(), + map.len() as u64, + 0, + map.len() as u64, + 1024, + ) + }, + "mapping routing fixture", + ) + .unwrap(); + let logits = Buffer::floats(4 * 256).unwrap(); + let input = (0..4 * 256) + .map(|i| ((i % 256) as f32 - 128.0) / 128.0) + .collect::>(); + logits.write_f32(&input).unwrap(); + let tokens = Buffer::bytes(16).unwrap(); + tokens.write_i32(&[1, 1000, 1002, 2]).unwrap(); + let selected = Buffer::bytes(4 * 6 * 4).unwrap(); + let weights = Buffer::floats(4 * 6).unwrap(); + let probs = Buffer::floats(4 * 256).unwrap(); + let bias = Buffer::floats(256).unwrap(); + bias.write_f32( + &(0..256) + .map(|i| { + if (200..206).contains(&i) { + 100.0 + i as f32 + } else { + 0.0 + } + }) + .collect::>(), + ) + .unwrap(); + for hash_mode in [false, true] { + let commands = Commands::begin().unwrap(); + call( + unsafe { + ds4_gpu_router_select_visual_batch_tensor( + selected.raw(), + weights.raw(), + probs.raw(), + map.as_ptr().cast(), + map.len() as u64, + 0, + hash_offset as u64, + 4, + 0, + 0, + true, + hash_mode, + logits.raw(), + tokens.raw(), + 256, + 6, + 1.5, + 4, + bias.raw(), + 1000, + ) + }, + "visual router fixture", + ) + .unwrap(); + commands.finish().unwrap(); + let mut ids = vec![0; 24]; + selected.read_i32(&mut ids).unwrap(); + let mut actual_weights = vec![0.0; 24]; + weights.read_f32(&mut actual_weights).unwrap(); + for row in 0..4 { + let expected = if row == 1 || row == 2 { + vec![205, 204, 203, 202, 201, 200] + } else if hash_mode { + let token = if row == 0 { 1 } else { 2 }; + (0..6).map(|e| 60 + token * 6 + e).collect() + } else { + vec![35, 34, 33, 32, 31, 30] + }; + assert_eq!(&ids[row * 6..row * 6 + 6], expected); + let probability = expected + .iter() + .map(|&id| input[row * 256 + id as usize].exp().ln_1p().sqrt()) + .collect::>(); + let sum = probability.iter().sum::().max(6.1035156e-5); + for i in 0..6 { + assert!((actual_weights[row * 6 + i] - probability[i] / sum * 1.5).abs() < 1e-6); + } + } + } + // Zero queries make the independent expected result the visible-key mean + // with one additional denominator term for the zero attention sink. + map[..8].fill(0); + let rows = 8_u32; + let heads = 2_u32; + let dim = 512_u32; + let pos = 10_u32; + let raw_cap = 32_u32; + let raw_start = 25_u32; + let n_raw = 18_u32; + let q = Buffer::floats(u64::from(rows * heads * dim)).unwrap(); + q.write_f32(&vec![0.0; (rows * heads * dim) as usize]) + .unwrap(); + let raw = Buffer::floats(u64::from(raw_cap * dim)).unwrap(); + let mut data = vec![0.0; (raw_cap * dim) as usize]; + for key in 0..n_raw { + let physical = (raw_start + key) % raw_cap; + data[(physical * dim) as usize..((physical + 1) * dim) as usize].fill(key as f32 / 64.0); + } + raw.write_f32(&data).unwrap(); + let comp = Buffer::bytes(4 * 512 * 2).unwrap(); + // Exactly representable half values: 0.25, 0.5, 0.75, 1.0. + let mut comp_bytes = Vec::new(); + for value in [0x3400_u16, 0x3800, 0x3a00, 0x3c00] { + for _ in 0..512 { + comp_bytes.extend(value.to_le_bytes()); + } + } + comp.write(0, &comp_bytes).unwrap(); + let out = Buffer::floats(u64::from(rows * heads * dim)).unwrap(); + let ids = [1, 1000, 1002, 1002, 1004, 2, 3, 4]; + let bounds = attention_bounds(&ids, 1000, pos, n_raw, 3).unwrap(); + let comp_mask = Buffer::floats(u64::from(rows) * 4).unwrap(); + comp_mask + .write_f32( + &(0..rows) + .flat_map(|row| { + (0..4).map(move |key| { + if key < (pos + row + 1) / 4 && key % 2 == 0 { + 0.0 + } else { + f32::NEG_INFINITY + } + }) + }) + .collect::>(), + ) + .unwrap(); + for (n_comp, indexed) in [(0_u32, false), (4, false), (4, true)] { + let mut mask = Vec::new(); + let mut expected = Vec::new(); + for row in 0..rows { + let mut values = Vec::new(); + for key in 0..n_raw { + let visible = + (bounds[row as usize * 2]..=bounds[row as usize * 2 + 1]).contains(&key); + mask.push(if visible { 0_u16 } else { 0xfc00 }); + if visible { + values.push(key as f32 / 64.0); + } + } + for key in 0..n_comp { + let visible = key < (pos + row + 1) / 4 && (!indexed || key % 2 == 0); + mask.push(if visible { 0 } else { 0xfc00 }); + if visible { + values.push((key + 1) as f32 / 4.0); + } + } + expected.push(values.iter().sum::() / (values.len() + 1) as f32); + } + let commands = Commands::begin().unwrap(); + call( + unsafe { + ds4_gpu_attention_visual_batch_tensor( + out.raw(), + map.as_ptr().cast(), + map.len() as u64, + 0, + q.raw(), + raw.raw(), + comp.raw(), + if indexed { + comp_mask.raw().cast_const() + } else { + std::ptr::null() + }, + u32::from(indexed), + rows, + pos, + n_raw, + raw_cap, + raw_start, + n_comp, + 3, + if n_comp == 0 { 0 } else { 4 }, + heads, + dim, + mask.as_ptr(), + mask.len() as u64, + ) + }, + "visual attention fixture", + ) + .unwrap(); + commands.finish().unwrap(); + let mut actual = vec![0.0; (rows * heads * dim) as usize]; + out.read_f32(&mut actual).unwrap(); + for (row, expected) in expected.iter().enumerate() { + for value in &actual[row * (heads * dim) as usize..(row + 1) * (heads * dim) as usize] { + assert!( + (value - expected).abs() < 0.0005, + "row {row}: {value} != {expected}" + ); + } + } + } + eprintln!( + "issue100 visual routing: hash/text/image rows and weights pass; raw/mixed/index-masked visual attention with wrapped KV passes" + ); +} + +#[test] +#[ignore = "requires the verified Vision-Exp managed artifacts and 110 GiB of Apple unified memory"] +fn issue100_full_model_images_cache_and_dspark() { + use crate::engine::{ChatTurn, Generator, metal::Executor, vision_data_marker}; + use crate::metrics::Metrics; + use crate::model::ModelChoice; + use crate::settings::{ + GenerationPreferences, ReasoningMode, RuntimePreferences, effective_settings, + }; + use base64::Engine as _; + use std::path::Path; + use std::sync::{Arc, atomic::AtomicBool}; + super::super::configure_sources().unwrap(); + let root = Path::new("local-eval-results/issue100-managed"); + let cache = Path::new("local-eval-results/issue100-full-model"); + std::fs::create_dir_all(cache).unwrap(); + let generation = GenerationPreferences { + context_tokens: 1024, + max_generated_tokens: 32, + system_prompt: String::new(), + temperature: Some(0.0), + reasoning_mode: ReasoningMode::Direct, + ..Default::default() + }; + let mut runtime = RuntimePreferences::default(); + runtime.execution.prefill_chunk = Some(384); + runtime.execution.power_percent = Some(100); + if std::env::var_os("DS4_ISSUE100_SSD").is_some() { + runtime.ssd.enabled = true; + runtime.ssd.cache = Some(crate::settings::StreamingCacheBudget::Experts(4096)); + runtime.ssd.preload_experts = Some(4096); + } + runtime.speculative.dspark_enabled = true; + runtime.speculative.dspark_confidence_threshold = Some(0.0); + runtime.speculative.keep_vision_loaded = std::env::var_os("DS4_ISSUE100_KEEP_VISION").is_some(); + let settings = effective_settings( + ModelChoice::DeepSeekV4FlashVision, + &generation, + &runtime, + root, + ) + .unwrap(); + let mut generator = Generator::open(&settings.engine, Arc::new(Metrics::new(cache))).unwrap(); + let image_marker = |name: &str| { + let bytes = std::fs::read(format!("docs/issue100-vision/images/{name}.png")).unwrap(); + vision_data_marker(&format!( + "data:image/png;base64,{}", + base64::engine::general_purpose::STANDARD.encode(bytes) + )) + }; + let turn = |content: String, tool: bool| ChatTurn { + user: !tool, + tool, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content, + }; + let red = turn( + format!( + "Describe the color and shape in this image in one sentence. {}", + image_marker("red-square") + ), + false, + ); + let blue = turn( + red.content + .replace(&image_marker("red-square"), &image_marker("blue-circle")), + false, + ); + let (red_tokens, red_overlays) = generator + .render_multimodal_conversation("", std::slice::from_ref(&red), ReasoningMode::Direct) + .unwrap(); + let (blue_tokens, blue_overlays) = generator + .render_multimodal_conversation("", std::slice::from_ref(&blue), ReasoningMode::Direct) + .unwrap(); + assert_eq!( + red_tokens, blue_tokens, + "same-size images intentionally have identical synthetic tokens" + ); + assert_eq!(red_overlays.len(), 1); + assert_eq!(blue_overlays.len(), 1); + let prompt_checkpoint = cache.join("prompt.kv"); + let restored_checkpoint = cache.join("restored.kv"); + let Executor::DeepSeek(executor) = &mut generator.executor else { + panic!("wrong executor") + }; + executor.set_vision_overlays(red_overlays).unwrap(); + executor.prefill(&red_tokens, |_| true).unwrap(); + assert_eq!(executor.vision_identities.len(), 1); + let image_identity = executor.vision_identities.clone(); + let prompt_logits = executor.logits.clone(); + executor + .save_checkpoint(&prompt_checkpoint, [100; 32], &mut |_| {}) + .unwrap(); + executor.reset().unwrap(); + assert!( + executor + .load_checkpoint(&prompt_checkpoint, &mut |_| {}) + .unwrap() + ); + assert_eq!(executor.logits, prompt_logits); + assert_eq!(executor.vision_identities, image_identity); + assert!(executor.vision_overlays.is_empty()); + // Verifier-sized text batches after disk restoration must retain image + // identities even though no image embeddings are needed for this suffix. + executor.prefill(&[19923, 14, 2058, 3], |_| true).unwrap(); + assert_eq!(executor.vision_identities, image_identity); + executor + .save_checkpoint(&restored_checkpoint, [100; 32], &mut |_| {}) + .unwrap(); + executor.reset().unwrap(); + assert!( + executor + .load_checkpoint(&restored_checkpoint, &mut |_| {}) + .unwrap() + ); + assert_eq!(executor.vision_identities, image_identity); + executor.set_vision_overlays(blue_overlays).unwrap(); + assert_eq!( + executor.session.position, 0, + "changed image must invalidate equal token prefix" + ); + let cancelled = AtomicBool::new(false); + let run = |g: &mut Generator, messages: &[ChatTurn], checkpoint: &Path| { + g.generate( + checkpoint, + None, + messages, + &settings.turn, + &cancelled, + |_, _| {}, + |_, _, _| {}, + |_| {}, + ) + .unwrap() + }; + let first = run( + &mut generator, + std::slice::from_ref(&red), + &cache.join("red.kv"), + ); + eprintln!("red: {}", first.message.content); + assert!(first.message.content.to_lowercase().contains("red")); + assert!(first.message.content.to_lowercase().contains("square")); + let stats = generator.executor.execution_stats(); + assert!( + stats.drafted_tokens > 0, + "the matching DSpark must actually draft" + ); + eprintln!( + "DSpark drafted={} accepted={}", + stats.drafted_tokens, stats.accepted_draft_tokens + ); + let Executor::DeepSeek(executor) = &mut generator.executor else { + unreachable!() + }; + executor.reset().unwrap(); + executor.dspark.as_mut().unwrap().strict = true; + let plain = run( + &mut generator, + std::slice::from_ref(&red), + &cache.join("red-target.kv"), + ); + assert_eq!( + plain.message.content, first.message.content, + "DSpark and target-only output" + ); + std::fs::copy(cache.join("red-target.kv"), cache.join("red-disk.kv")).unwrap(); + let history = [ + red.clone(), + plain.message.clone(), + turn( + "What color was the shape? Answer with one word.".into(), + false, + ), + ]; + let warm = run(&mut generator, &history, &cache.join("red-target.kv")); + assert!( + warm.cached_tokens > 0, + "warm image history must reuse its KV prefix" + ); + assert!(warm.message.content.to_lowercase().contains("red")); + generator.executor.reset().unwrap(); + generator.checkpoint = None; + let restored = run(&mut generator, &history, &cache.join("red-disk.kv")); + assert!( + restored.cached_tokens > 0, + "disk image history must reuse its KV prefix" + ); + assert_eq!(restored.message.content, warm.message.content); + let mut append_history = history.to_vec(); + append_history.push(restored.message.clone()); + append_history.push(turn( + format!( + "Name the color in this new image. {}", + image_marker("blue-circle") + ), + false, + )); + let appended = run(&mut generator, &append_history, &cache.join("red-disk.kv")); + assert!( + appended.cached_tokens > 0, + "appending an image must retain the previous image/text frontier" + ); + assert!(appended.message.content.to_lowercase().contains("blue")); + append_history.push(appended.message.clone()); + append_history.push(turn( + format!( + "The tool returned this image. Describe its color. {}", + image_marker("red-square") + ), + true, + )); + let appended_tool = run(&mut generator, &append_history, &cache.join("red-disk.kv")); + assert!( + appended_tool.cached_tokens > 0, + "tool image append must retain the live frontier" + ); + assert!(appended_tool.message.content.to_lowercase().contains("red")); + + let changed = run( + &mut generator, + std::slice::from_ref(&blue), + &cache.join("blue.kv"), + ); + eprintln!("blue: {}", changed.message.content); + assert!(changed.message.content.to_lowercase().contains("blue")); + assert!(changed.message.content.to_lowercase().contains("circle")); + let pair = turn( + format!( + "Name the color of the first shape, then the second shape. {} {}", + image_marker("red-square"), + image_marker("blue-circle") + ), + false, + ); + let both = run(&mut generator, &[pair], &cache.join("pair.kv")); + eprintln!("pair: {}", both.message.content); + let answer = both.message.content.to_lowercase(); + assert!(answer.contains("red") && answer.contains("blue")); + assert!(answer.find("red").unwrap() < answer.find("blue").unwrap()); + let swapped = turn( + format!( + "Name the color of the first shape, then the second shape. {} {}", + image_marker("blue-circle"), + image_marker("red-square") + ), + false, + ); + let (swapped_tokens, swapped_overlays) = generator + .render_multimodal_conversation("", std::slice::from_ref(&swapped), ReasoningMode::Direct) + .unwrap(); + let Executor::DeepSeek(executor) = &mut generator.executor else { + unreachable!() + }; + assert!( + executor.tokens.starts_with(&swapped_tokens), + "swapping equal-sized images preserves the prompt IDs" + ); + executor.set_vision_overlays(swapped_overlays).unwrap(); + assert_eq!( + executor.session.position, 0, + "reordered images must invalidate cached KV" + ); + + let tool = [ + turn( + "Name the color in the image returned by the tool.".into(), + false, + ), + turn(image_marker("blue-circle"), true), + ]; + let tool_output = run(&mut generator, &tool, &cache.join("tool.kv")); + eprintln!("tool image: {}", tool_output.message.content); + assert!(tool_output.message.content.to_lowercase().contains("blue")); + let text_only = run( + &mut generator, + &[turn( + "What is 2 + 2? Reply with just the number.".into(), + false, + )], + &cache.join("text.kv"), + ); + assert!(text_only.message.content.contains('4')); + let Executor::DeepSeek(executor) = &generator.executor else { + unreachable!() + }; + assert!(executor.vision_identities.is_empty()); + eprintln!( + "issue100: production rendering, image recognition, multi-image/tool input, same-layout image invalidation, disk/warm KV and DSpark target equivalence passed" + ); +} diff --git a/src/engine/metal/vision.rs b/src/engine/metal/vision.rs index 45525ef..0d3d70b 100644 --- a/src/engine/metal/vision.rs +++ b/src/engine/metal/vision.rs @@ -15,7 +15,10 @@ pub(super) struct VisionEncoder { pub(in crate::engine) struct VisionEmbedding { pub(in crate::engine) values: Vec, + pub(in crate::engine) fingerprint: [u8; 32], pub(in crate::engine) tokens: u32, + pub(in crate::engine) grid_width: u32, + pub(in crate::engine) grid_height: u32, pub(in crate::engine) width: u32, pub(in crate::engine) height: u32, pub(in crate::engine) content_width: u32, @@ -100,6 +103,9 @@ impl VisionEncoder { "encoding a GLM 5.3 image", )?; Ok(VisionEmbedding { + fingerprint: [0; 32], + grid_width: 0, + grid_height: 0, values, tokens, width, @@ -241,7 +247,7 @@ fn smart_resize(height: u32, width: u32) -> Result<(u32, u32), String> { } #[allow(clippy::too_many_arguments)] -fn resize_bicubic( +pub(in crate::engine) fn resize_bicubic( source: &[u8], source_width: u32, source_height: u32, diff --git a/src/engine/tokenizer.rs b/src/engine/tokenizer.rs index 7f154e9..55b2581 100644 --- a/src/engine/tokenizer.rs +++ b/src/engine/tokenizer.rs @@ -433,6 +433,32 @@ impl Tokenizer { messages: &[ChatTurn], reasoning: ReasoningMode, continue_assistant: bool, + ) -> Vec { + self.encode_messages_inner( + system_prompt, + messages, + reasoning, + continue_assistant, + false, + ) + } + + pub(super) fn encode_deepseek_vision_conversation( + &self, + system: &str, + messages: &[ChatTurn], + reasoning: ReasoningMode, + ) -> Vec { + self.encode_messages_inner(system, messages, reasoning, true, true) + } + + fn encode_messages_inner( + &self, + system_prompt: &str, + messages: &[ChatTurn], + reasoning: ReasoningMode, + continue_assistant: bool, + vision: bool, ) -> Vec { if self.family == ModelFamily::Qwen { return self.encode_qwen_messages( @@ -481,13 +507,14 @@ impl Tokenizer { } else { output.push(self.user); output.extend(self.tokenize("")); - output.extend( - self.tokenize( - &message - .content - .replace("", "</tool_result>"), - ), - ); + let content = message + .content + .replace("", "</tool_result>"); + if vision && content.contains(VISION_TOKEN_START) { + self.tokenize_vision_text(&content, &mut output); + } else { + output.extend(self.tokenize(&content)); + } output.extend(self.tokenize("")); } continue; @@ -510,7 +537,11 @@ impl Tokenizer { output.push(self.think_end); } } - output.extend(self.tokenize_rendered(&message.content)); + if vision && message.user && message.content.contains(VISION_TOKEN_START) { + self.tokenize_vision_text(&message.content, &mut output); + } else { + output.extend(self.tokenize_rendered(&message.content)); + } if !message.user && self.family == ModelFamily::DeepSeek && (continue_assistant || index + 1 < messages.len()) @@ -531,6 +562,28 @@ impl Tokenizer { output } + fn tokenize_vision_text(&self, text: &str, output: &mut Vec) { + let mut rest = text; + while let Some(start) = rest.find(VISION_TOKEN_START) { + let tail = &rest[start + VISION_TOKEN_START.len()..]; + let Some(end) = tail.find(VISION_TOKEN_END) else { + break; + }; + let Ok(count) = tail[..end].parse::() else { + break; + }; + if count > 384 { + break; + } + output.extend(self.tokenize(&rest[..start])); + output.push(VISION_START_TOKEN); + output.extend(std::iter::repeat_n(VISION_IMAGE_TOKEN, count)); + output.push(VISION_END_TOKEN); + rest = &tail[end + VISION_TOKEN_END.len()..]; + } + output.extend(self.tokenize(rest)); + } + fn encode_qwen_messages( &self, system_prompt: Option<&str>, @@ -646,6 +699,42 @@ impl Tokenizer { prompt: &str, reasoning: ReasoningMode, skip_previous_eos: bool, + ) -> Vec { + self.encode_continuation_inner(prompt, reasoning, skip_previous_eos, false) + } + + pub(super) fn encode_deepseek_vision_continuation( + &self, + prompt: &str, + reasoning: ReasoningMode, + skip_previous_eos: bool, + ) -> Vec { + self.encode_continuation_inner(prompt, reasoning, skip_previous_eos, true) + } + + pub(super) fn encode_deepseek_vision_tool_continuation( + &self, + message: &ChatTurn, + reasoning: ReasoningMode, + ) -> Vec { + let mut output = + self.encode_deepseek_vision_conversation("", std::slice::from_ref(message), reasoning); + // A standalone conversation starts with BOS. The live frontier instead + // needs the preceding assistant's EOS, just like a user continuation. + if message.skip_previous_eos { + output.remove(0); + } else { + output[0] = self.eos; + } + output + } + + fn encode_continuation_inner( + &self, + prompt: &str, + reasoning: ReasoningMode, + skip_previous_eos: bool, + vision: bool, ) -> Vec { if self.family == ModelFamily::Qwen { let messages = [ChatTurn { @@ -664,7 +753,11 @@ impl Tokenizer { output.push(self.eos); } output.push(self.user); - output.extend(self.tokenize_rendered(prompt)); + if vision && prompt.contains(VISION_TOKEN_START) { + self.tokenize_vision_text(prompt, &mut output); + } else { + output.extend(self.tokenize_rendered(prompt)); + } output.push(self.assistant); if reasoning != ReasoningMode::Direct { output.push(self.think_start); @@ -1238,6 +1331,95 @@ fn unicode_punctuation(cp: u32) -> bool { mod tests { use super::*; + #[test] + #[ignore = "CPU-only; requires the installed 0731 tokenizer shared with Vision-Exp"] + fn issue100_visual_text_preserves_image_boundaries() { + use super::super::Model; + use crate::model::ModelChoice; + let path = crate::model::engine_artifacts( + ModelChoice::DeepSeekV4Flash0731, + false, + &crate::app::models_path(), + ) + .model; + let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap(); + let tokenizer = &model.tokenizer; + let marker = format!("{VISION_TOKEN_START}2{VISION_TOKEN_END}"); + let prompt = format!("before{marker}after"); + for reasoning in ModelChoice::DeepSeekV4FlashVision.reasoning_modes() { + for skip_eos in [false, true] { + let mut expected = Vec::new(); + if !skip_eos { + expected.push(tokenizer.eos); + } + expected.push(tokenizer.user); + expected.extend(tokenizer.tokenize("before")); + expected.extend([ + VISION_START_TOKEN, + VISION_IMAGE_TOKEN, + VISION_IMAGE_TOKEN, + VISION_END_TOKEN, + ]); + expected.extend(tokenizer.tokenize("after")); + expected.push(tokenizer.assistant); + expected.push(if *reasoning == ReasoningMode::Direct { + tokenizer.think_end + } else { + tokenizer.think_start + }); + assert_eq!( + tokenizer.encode_deepseek_vision_continuation(&prompt, *reasoning, skip_eos), + expected + ); + assert_eq!( + tokenizer.encode_deepseek_vision_continuation( + "plain text", + *reasoning, + skip_eos + ), + tokenizer.encode_continuation("plain text", *reasoning, skip_eos) + ); + } + } + let message = ChatTurn { + user: false, + tool: true, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: format!("{marker}"), + }; + let actual = tokenizer.encode_deepseek_vision_conversation( + "", + std::slice::from_ref(&message), + ReasoningMode::Direct, + ); + let mut expected = vec![tokenizer.bos, tokenizer.user]; + expected.extend(tokenizer.tokenize("")); + expected.extend([ + VISION_START_TOKEN, + VISION_IMAGE_TOKEN, + VISION_IMAGE_TOKEN, + VISION_END_TOKEN, + ]); + expected.extend(tokenizer.tokenize("</tool_result>")); + expected.extend(tokenizer.tokenize("")); + expected.extend([tokenizer.assistant, tokenizer.think_end]); + assert_eq!(actual, expected); + expected[0] = tokenizer.eos; + assert_eq!( + tokenizer.encode_deepseek_vision_tool_continuation(&message, ReasoningMode::Direct), + expected + ); + let mut message = message; + message.skip_previous_eos = true; + assert_eq!( + tokenizer.encode_deepseek_vision_tool_continuation(&message, ReasoningMode::Direct), + expected[1..] + ); + } + #[test] #[ignore = "CPU-only; requires DS4SERVER_CHAT_REFERENCE and its installed GGUF"] fn ds4_chat_matches_original_session_tokens() { diff --git a/src/engine/validation.rs b/src/engine/validation.rs index fd1c9d8..c1a9a39 100644 --- a/src/engine/validation.rs +++ b/src/engine/validation.rs @@ -9,6 +9,7 @@ pub(crate) fn validate_model_artifact( let model = Gguf::open(path)?; let shape = match expected { ModelChoice::DeepSeekV4Flash0731 => FLASH_0731, + ModelChoice::DeepSeekV4FlashVision => FLASH_VISION, ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 | ModelChoice::Glm53Flash @@ -40,6 +41,9 @@ pub(crate) fn validate_model_artifact( pub(crate) fn validate_vision_artifact(path: &Path) -> Result<(), String> { let model = Gguf::open(path)?; + if model.bytes("general.architecture")? == b"deepseek4-vision" { + return validate_deepseek_vision(&model); + } if model.bytes("general.architecture")? != b"glm5-next-vision" { return Err("vision GGUF architecture is not glm5-next-vision".into()); } @@ -193,11 +197,21 @@ pub(super) fn validate_support(model: &Gguf, shape: &Shape) -> Result Result { let architecture = model.bytes("general.architecture")?; + let variant = model.metadata.contains_key("deepseek4.checkpoint_variant"); + if variant && (architecture != b"deepseek4" || model.u32("deepseek4.block_count")? != 43) { + return Err("Vision-Exp metadata requires a DeepSeek V4 Flash checkpoint".into()); + } let shape = match architecture { b"glm-dsa" => GLM, b"glm5-next" => GLM53_FLASH, _ => match (model.u32("deepseek4.block_count")?, expected) { - (43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731, + (43, _) if variant => { + validate_vision_checkpoint(model)?; + if !model.boolean("deepseek4.vision.sidecar_required")? { + return Err("DeepSeek Vision-Exp requires its sidecar metadata".into()); + } + FLASH_VISION + } (43, _) => FLASH_0731, (61, _) => PRO, (layers, _) => return Err(format!("unsupported DeepSeek layer count: {layers}")), @@ -210,6 +224,14 @@ pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result Result<(), String> { } pub(super) fn validate_dspark(model: &Gguf, shape: &Shape) -> Result<(), String> { - if !matches!(shape.model, ModelChoice::DeepSeekV4Flash0731) { + if !shape.model.supports_dspark() { return Err("DSpark support is available only for DeepSeek V4 Flash".into()); } + if shape.model == ModelChoice::DeepSeekV4FlashVision { + validate_vision_checkpoint(model)?; + } else if model.metadata.contains_key("deepseek4.checkpoint_variant") { + return Err("Vision-Exp DSpark support cannot be used with another checkpoint".into()); + } let DsparkConfig { block_size, markov_rank, @@ -1060,14 +1087,18 @@ fn float_eq(actual: f32, expected: f32) -> bool { fn compression_ratio(shape: &Shape, layer: u32) -> u32 { match shape.model { - ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0, + ModelChoice::DeepSeekV4FlashVision | ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0, ModelChoice::DeepSeekV4Pro if layer < 2 => 128, - ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro + ModelChoice::DeepSeekV4FlashVision + | ModelChoice::DeepSeekV4Flash0731 + | ModelChoice::DeepSeekV4Pro if layer.is_multiple_of(2) => { 4 } - ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro => 128, + ModelChoice::DeepSeekV4FlashVision + | ModelChoice::DeepSeekV4Flash0731 + | ModelChoice::DeepSeekV4Pro => 128, ModelChoice::Glm52 | ModelChoice::Glm53Flash | ModelChoice::Qwen38FlashNext => 0, } } @@ -1084,10 +1115,154 @@ fn first_u32s<'a>(model: &'a Gguf, keys: &[&str]) -> Result<&'a [u32], String> { .ok_or_else(|| format!("required DSpark metadata is missing: {}", keys[0])) } +fn validate_vision_checkpoint(model: &Gguf) -> Result<(), String> { + if model.bytes("deepseek4.checkpoint_variant")? != b"vision-exp" + || model.bytes("general.source.revision")? != deepseek_vision::SOURCE_REVISION + { + return Err( + "DeepSeek Vision-Exp checkpoint does not match the pinned source revision".into(), + ); + } + Ok(()) +} + +fn validate_deepseek_vision(model: &Gguf) -> Result<(), String> { + if model.bytes("deepseek4-vision.checkpoint_variant")? != b"vision-exp" + || model.bytes("general.source.revision")? != deepseek_vision::SOURCE_REVISION + { + return Err("DeepSeek vision encoder does not match the pinned checkpoint".into()); + } + if model.tensors.len() != 316 { + return Err(format!( + "DeepSeek vision encoder has {} tensors, expected 316", + model.tensors.len() + )); + } + for (key, value) in [ + ("block_count", 32), + ("embedding_length", 1024), + ("feed_forward_length", 2816), + ("attention.head_count", 16), + ("projection_length", 4096), + ("patch_size", 14), + ("downsample_ratio", 3), + ("image.max_tokens", 384), + ("image.min_pixels", 147456), + ("image.max_width_height_ratio", 8), + ("language.block_count", 43), + ("language.expert_count", 256), + ] { + expect_u64(model, &format!("deepseek4-vision.{key}"), value)?; + } + expect_float( + model, + "deepseek4-vision.attention.layer_norm_rms_epsilon", + 1e-6, + )?; + expect_float(model, "deepseek4-vision.rope.freq_base", 10000.0)?; + for (name, dims) in [ + ("vision.patch_embed.proj.weight", vec![588, 1024]), + ("vision.patch_embed.proj.bias", vec![1024]), + ("vision.norm.weight", vec![1024]), + ("aligner.w1.weight", vec![9216, 4096]), + ("aligner.w1.bias", vec![4096]), + ("aligner.w2.weight", vec![4096, 4096]), + ("aligner.w2.bias", vec![4096]), + ("image_start", vec![4096]), + ("image_pad", vec![4096]), + ("image_newline", vec![4096]), + ("image_end", vec![4096]), + ] { + expect(model, name, &[BF16], &dims)?; + } + for layer in 0..32 { + for (suffix, dims) in [ + ("norm1.weight", vec![1024]), + ("attn.wqkv.weight", vec![1024, 3072]), + ("attn.wqkv.bias", vec![3072]), + ("attn.wo.weight", vec![1024, 1024]), + ("attn.wo.bias", vec![1024]), + ("norm2.weight", vec![1024]), + ("mlp.w1.weight", vec![1024, 5632]), + ("mlp.w2.weight", vec![2816, 1024]), + ] { + expect( + model, + &format!("vision.blocks.{layer}.{suffix}"), + &[BF16], + &dims, + )?; + } + } + for layer in 0..43 { + expect( + model, + &format!("layers.{layer}.ffn.gate.bias_vl"), + &[F32], + &[256], + )?; + } + for stage in 0..3 { + expect( + model, + &format!("mtp.{stage}.ffn.gate.bias_vl"), + &[F32], + &[256], + )?; + expect( + model, + &format!("layers.{stage}.ffn.gate.bias"), + &[F32], + &[256], + )?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; + #[test] + #[ignore = "requires the pinned Vision-Exp encoder and DSpark support GGUFs"] + fn issue100_checkpoint_metadata_is_strict() { + let root = Path::new("local-eval-results/issue100-artifacts"); + let mut sidecar = Gguf::open(&root.join("DeepSeek-V4-Flash-Vision-Encoder.gguf")).unwrap(); + validate_deepseek_vision(&sidecar).unwrap(); + let revision = sidecar + .metadata + .insert( + "general.source.revision".into(), + Value::Bytes(b"wrong".to_vec()), + ) + .unwrap(); + assert!(validate_deepseek_vision(&sidecar).is_err()); + sidecar + .metadata + .insert("general.source.revision".into(), revision); + sidecar.tensors.remove("layers.42.ffn.gate.bias_vl"); + assert!(validate_deepseek_vision(&sidecar).is_err()); + let mut support = + Gguf::open(&root.join("DeepSeek-V4-Flash-Vision-Exp-DSpark-support.gguf")).unwrap(); + assert!(matches!( + validate_support(&support, &FLASH_VISION).unwrap(), + SupportKind::DSpark + )); + assert!(validate_support(&support, &FLASH_0731).is_err()); + support.metadata.remove("deepseek4.checkpoint_variant"); + assert!(validate_support(&support, &FLASH_VISION).is_err()); + let old = crate::model::engine_artifacts( + ModelChoice::DeepSeekV4Flash0731, + true, + &crate::app::models_path(), + ) + .support + .unwrap(); + if old.exists() { + assert!(validate_support(&Gguf::open(&old).unwrap(), &FLASH_VISION).is_err()); + } + } + #[test] fn mxfp4_is_limited_to_routed_experts() { let tensor = Tensor { diff --git a/src/metrics.rs b/src/metrics.rs index c064249..87df1ca 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -938,6 +938,7 @@ fn model_code(model: ModelChoice) -> u8 { ModelChoice::DeepSeekV4Flash0731 => 4, ModelChoice::Glm53Flash => 5, ModelChoice::Qwen38FlashNext => 6, + ModelChoice::DeepSeekV4FlashVision => 7, } } @@ -949,6 +950,7 @@ fn model_name(value: u8) -> &'static str { 4 => "DeepSeek V4 Flash 0731", 5 => "GLM 5.3 Flash Q2", 6 => "Qwen 3.8 Flash Next", + 7 => "DeepSeek V4 Flash Vision Experimental", _ => "No model loaded", } } diff --git a/src/model.rs b/src/model.rs index 4a9edda..2e61ffd 100644 --- a/src/model.rs +++ b/src/model.rs @@ -10,16 +10,20 @@ use std::fmt; use std::fs; use std::path::{Path, PathBuf}; -pub(crate) const MODEL_CHOICES: [ModelChoice; 5] = [ +pub(crate) const MODEL_CHOICES: [ModelChoice; 6] = [ ModelChoice::DeepSeekV4Flash0731, + ModelChoice::DeepSeekV4FlashVision, ModelChoice::DeepSeekV4Pro, ModelChoice::Glm52, ModelChoice::Glm53Flash, ModelChoice::Qwen38FlashNext, ]; -pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 8] = [ +pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 11] = [ ManagedArtifactId::DeepSeekV4Flash0731, ManagedArtifactId::DeepSeekV4Flash0731Dspark, + ManagedArtifactId::DeepSeekV4FlashVision, + ManagedArtifactId::DeepSeekV4FlashVisionEncoder, + ManagedArtifactId::DeepSeekV4FlashVisionDspark, ManagedArtifactId::DeepSeekV4Pro, ManagedArtifactId::Glm52, ManagedArtifactId::Glm53Flash, @@ -50,6 +54,33 @@ const FLASH_0731_DSPARK: Artifact = Artifact { support: Some(true), vision: false, }; +const FLASH_VISION: Artifact = Artifact { + label: "DeepSeek V4 Flash Vision Experimental model", + file_name: "DeepSeek-V4-Flash-Vision-Exp-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8.gguf", + repository: DEEPSEEK_REPOSITORY, + size: 86_720_111_776, + sha256: "8f2d42c0071ccf8a98f391cc2b835fd123f12330690b3059dbb7707920e5ad9e", + support: Some(false), + vision: false, +}; +const FLASH_VISION_ENCODER: Artifact = Artifact { + label: "DeepSeek V4 Flash Vision Experimental encoder", + file_name: "DeepSeek-V4-Flash-Vision-Encoder.gguf", + repository: DEEPSEEK_REPOSITORY, + size: 932_857_760, + sha256: "00cd4d81a435364967400a95c42703343e11da6b6f18c5143fe76e1d94d5035f", + support: None, + vision: true, +}; +const FLASH_VISION_DSPARK: Artifact = Artifact { + label: "DeepSeek V4 Flash Vision Experimental DSpark support", + file_name: "DeepSeek-V4-Flash-Vision-Exp-DSpark-support.gguf", + repository: DEEPSEEK_REPOSITORY, + size: 5_989_114_528, + sha256: "0807a67fd9ce5874bfc60d8d2461f50e11657e3dd94913d3473f85aa679bc877", + support: Some(true), + vision: false, +}; const PRO: Artifact = Artifact { label: "DeepSeek V4 Pro 0813 model", file_name: "DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix-0813.gguf", @@ -92,6 +123,8 @@ pub(crate) enum ModelChoice { #[default] #[serde(rename = "deepseek-v4-flash-0731")] DeepSeekV4Flash0731, + #[serde(rename = "deepseek-v4-flash-vision-exp")] + DeepSeekV4FlashVision, #[serde(rename = "deepseek-v4-pro")] DeepSeekV4Pro, #[serde(rename = "glm-5.2")] @@ -106,6 +139,7 @@ impl ModelChoice { pub(crate) fn id(self) -> &'static str { match self { Self::DeepSeekV4Flash0731 => "deepseek-v4-flash-0731", + Self::DeepSeekV4FlashVision => "deepseek-v4-flash-vision-exp", Self::DeepSeekV4Pro => "deepseek-v4-pro", Self::Glm52 => "glm-5.2", Self::Glm53Flash => "glm-5.3-flash", @@ -118,7 +152,10 @@ impl ModelChoice { } pub(crate) fn supports_dspark(self) -> bool { - self == Self::DeepSeekV4Flash0731 + matches!( + self, + Self::DeepSeekV4Flash0731 | Self::DeepSeekV4FlashVision + ) } pub(crate) fn is_glm(self) -> bool { @@ -130,7 +167,10 @@ impl ModelChoice { } pub(crate) fn supports_vision(self) -> bool { - matches!(self, Self::Glm53Flash | Self::Qwen38FlashNext) + matches!( + self, + Self::DeepSeekV4FlashVision | Self::Glm53Flash | Self::Qwen38FlashNext + ) } pub(crate) fn supports_integrated_mtp(self) -> bool { @@ -148,6 +188,7 @@ impl ModelChoice { fn main_artifact(self) -> &'static Artifact { match self { Self::DeepSeekV4Flash0731 => &FLASH_0731, + Self::DeepSeekV4FlashVision => &FLASH_VISION, Self::DeepSeekV4Pro => &PRO, Self::Glm52 => &GLM, Self::Glm53Flash => &GLM53_FLASH, @@ -158,12 +199,17 @@ impl ModelChoice { fn dspark_artifact(self) -> Option<&'static Artifact> { match self { Self::DeepSeekV4Flash0731 => Some(&FLASH_0731_DSPARK), + Self::DeepSeekV4FlashVision => Some(&FLASH_VISION_DSPARK), Self::DeepSeekV4Pro | Self::Glm52 | Self::Glm53Flash | Self::Qwen38FlashNext => None, } } fn vision_artifact(self) -> Option<&'static Artifact> { - (self == Self::Glm53Flash).then_some(&GLM53_FLASH_VISION) + match self { + Self::DeepSeekV4FlashVision => Some(&FLASH_VISION_ENCODER), + Self::Glm53Flash => Some(&GLM53_FLASH_VISION), + _ => None, + } } #[cfg(test)] @@ -252,6 +298,9 @@ pub(crate) fn validate_engine_artifacts( path.display() )), }?; + if model == ModelChoice::DeepSeekV4FlashVision && artifacts.vision.is_none() { + return Err(format!("{model} requires its vision encoder sidecar")); + } if let Some(path) = artifacts.vision.as_deref() { model .vision_artifact() @@ -263,6 +312,9 @@ pub(crate) fn validate_engine_artifacts( #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum ManagedArtifactId { + DeepSeekV4FlashVision, + DeepSeekV4FlashVisionEncoder, + DeepSeekV4FlashVisionDspark, DeepSeekV4Flash0731, DeepSeekV4Flash0731Dspark, DeepSeekV4Pro, @@ -283,6 +335,9 @@ impl ManagedArtifactId { Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Flash0731Dspark => { ModelChoice::DeepSeekV4Flash0731 } + Self::DeepSeekV4FlashVision + | Self::DeepSeekV4FlashVisionEncoder + | Self::DeepSeekV4FlashVisionDspark => ModelChoice::DeepSeekV4FlashVision, Self::DeepSeekV4Pro => ModelChoice::DeepSeekV4Pro, Self::Glm52 => ModelChoice::Glm52, Self::Glm53Flash | Self::Glm53FlashVision => ModelChoice::Glm53Flash, @@ -293,7 +348,10 @@ impl ManagedArtifactId { fn artifact(self) -> &'static Artifact { match self { Self::DeepSeekV4Flash0731 => &FLASH_0731, + Self::DeepSeekV4FlashVision => &FLASH_VISION, Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK, + Self::DeepSeekV4FlashVisionEncoder => &FLASH_VISION_ENCODER, + Self::DeepSeekV4FlashVisionDspark => &FLASH_VISION_DSPARK, Self::DeepSeekV4Pro => &PRO, Self::Glm52 => &GLM, Self::Glm53Flash => &GLM53_FLASH, @@ -397,6 +455,7 @@ impl fmt::Display for ModelChoice { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(match self { Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731", + Self::DeepSeekV4FlashVision => "DeepSeek V4 Flash Vision Experimental", Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813", Self::Glm52 => "GLM 5.2", Self::Glm53Flash => "GLM 5.3 Flash Q2", @@ -537,6 +596,8 @@ pub(crate) fn installed_models(models_path: &Path) -> Vec { qwen::is_installed(&qwen::root(models_path)) } else { model.main_artifact().is_installed(*model, models_path) + && (*model != ModelChoice::DeepSeekV4FlashVision + || FLASH_VISION_ENCODER.is_installed(*model, models_path)) } }) .collect() diff --git a/src/model/transfer.rs b/src/model/transfer.rs index 5b609e8..d77baeb 100644 --- a/src/model/transfer.rs +++ b/src/model/transfer.rs @@ -492,8 +492,8 @@ mod tests { Some(ModelChoice::DeepSeekV4Flash0731) ); assert!(ModelChoice::from_id("unknown").is_none()); - assert_eq!(MODEL_CHOICES.len(), 5); - assert_eq!(MANAGED_ARTIFACTS.len(), 8); + assert_eq!(MODEL_CHOICES.len(), 6); + assert_eq!(MANAGED_ARTIFACTS.len(), 11); assert_eq!( ModelChoice::from_id("qwen3.8-flash-next"), Some(ModelChoice::Qwen38FlashNext) @@ -642,6 +642,99 @@ mod tests { fs::remove_dir_all(models).unwrap(); } + #[test] + fn issue100_catalog_keeps_vision_and_dspark_checkpoint_specific() { + let model = ModelChoice::DeepSeekV4FlashVision; + assert_eq!(ModelChoice::from_id(model.id()), Some(model)); + assert_eq!( + serde_json::from_str::(&serde_json::to_string(&model).unwrap()).unwrap(), + model + ); + assert!(model.supports_vision() && model.supports_dspark()); + assert!(!model.supports_integrated_mtp()); + assert_eq!(model.artifacts(true).count(), 3); + assert_eq!(model.artifacts(false).count(), 2); + assert_ne!( + model.dspark_artifact().unwrap().sha256, + FLASH_0731_DSPARK.sha256 + ); + let defaults = crate::config::ModelPreferences::defaults_for(model); + assert!(defaults.speculative.dspark_enabled); + assert!(defaults.speculative.validate(model).is_ok()); + let root = std::env::temp_dir().join(format!("issue100-catalog-{}", std::process::id())); + fs::create_dir_all(root.join(model.id())).unwrap(); + // Sparse files exercise catalog and marker bookkeeping only. Real GGUF + // content and hashes are checked by the separate managed-intake test. + for artifact in [&FLASH_VISION, &FLASH_VISION_DSPARK] { + File::create(artifact.path(model, &root)) + .unwrap() + .set_len(artifact.size) + .unwrap(); + mark_verified(model, artifact, &root).unwrap(); + } + assert!(!installed_models(&root).contains(&model)); + let missing = engine_artifacts(model, true, &root); + assert!( + validate_engine_artifacts(model, true, &missing) + .unwrap_err() + .contains("requires its vision encoder") + ); + File::create(FLASH_VISION_ENCODER.path(model, &root)) + .unwrap() + .set_len(FLASH_VISION_ENCODER.size) + .unwrap(); + mark_verified(model, &FLASH_VISION_ENCODER, &root).unwrap(); + assert_eq!(installed_models(&root), [model]); + let mut artifacts = engine_artifacts(model, true, &root); + validate_engine_artifacts(model, true, &artifacts).unwrap(); + artifacts.support = Some(FLASH_0731_DSPARK.path(ModelChoice::DeepSeekV4Flash0731, &root)); + assert!( + validate_engine_artifacts(model, true, &artifacts) + .unwrap_err() + .contains("not the expected") + ); + artifacts = engine_artifacts(model, false, &root); + artifacts.vision = Some(GLM53_FLASH_VISION.path(ModelChoice::Glm53Flash, &root)); + assert!( + validate_engine_artifacts(model, false, &artifacts) + .unwrap_err() + .contains("not the expected") + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + #[ignore = "requires all three pinned Vision-Exp GGUF artifacts; performs full SHA256 and intake validation"] + fn issue100_managed_intake() { + let source = Path::new("local-eval-results/issue100-artifacts"); + let root = Path::new("local-eval-results/issue100-managed"); + let model = ModelChoice::DeepSeekV4FlashVision; + fs::create_dir_all(root.join(model.id())).unwrap(); + for id in [ + ManagedArtifactId::DeepSeekV4FlashVisionEncoder, + ManagedArtifactId::DeepSeekV4FlashVisionDspark, + ManagedArtifactId::DeepSeekV4FlashVision, + ] { + let artifact = id.artifact(); + let destination = artifact.path(model, root); + if !destination.exists() { + fs::hard_link(source.join(artifact.file_name), &destination).unwrap(); + } + let verified = AtomicU64::new(0); + assert_eq!( + validate_managed_artifact(id, root, &AtomicBool::new(false), &verified).unwrap(), + DownloadOutcome::Complete + ); + assert_eq!(verified.load(Ordering::Relaxed), artifact.size); + eprintln!("verified {} {} bytes", artifact.file_name, artifact.size); + } + assert_eq!(installed_models(root), [model]); + for dspark in [false, true] { + validate_engine_artifacts(model, dspark, &engine_artifacts(model, dspark, root)) + .unwrap(); + } + } + #[test] fn verification_reports_bytes_read() { let path = std::env::temp_dir().join(format!( diff --git a/src/model_eval.rs b/src/model_eval.rs index 73c004b..c64b598 100644 --- a/src/model_eval.rs +++ b/src/model_eval.rs @@ -249,7 +249,7 @@ fn print_usage() { println!( "Headless model evaluation\n\n\ Usage: ds4-server model-eval --model MODEL [OPTIONS]\n\n\ -Models: deepseek-v4-flash-0731, deepseek-v4-pro, glm-5.2, glm-5.3-flash, qwen3.8-flash-next\n\n\ +Models: deepseek-v4-flash-0731, deepseek-v4-flash-vision-exp, deepseek-v4-pro, glm-5.2, glm-5.3-flash, qwen3.8-flash-next\n\n\ Options:\n\ --prompt TEXT Chat turn; repeat for one ongoing conversation\n\ (default: autonomous short story)\n\ diff --git a/src/settings.rs b/src/settings.rs index ebf5c57..f33007b 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -52,7 +52,7 @@ impl SpeculativePreferences { return Err("Integrated MTP is unavailable for the selected model.".into()); } if self.keep_vision_loaded && !model.supports_vision() { - return Err("Persistent vision weights are available only for GLM 5.3 Flash and Qwen 3.8 Flash Next.".into()); + return Err("Persistent vision weights require a supported vision model.".into()); } if self.dspark_enabled && !model.supports_dspark() { return Err("DSpark is not available for the selected model.".into()); @@ -543,7 +543,9 @@ impl ReasoningMode { impl ModelChoice { pub(crate) fn reasoning_modes(self) -> &'static [ReasoningMode] { match self { - Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Pro => &DEEPSEEK_REASONING_MODES, + Self::DeepSeekV4Flash0731 | Self::DeepSeekV4FlashVision | Self::DeepSeekV4Pro => { + &DEEPSEEK_REASONING_MODES + } Self::Glm52 => &GLM_52_REASONING_MODES, Self::Glm53Flash => &GLM_53_REASONING_MODES, Self::Qwen38FlashNext => &QWEN_REASONING_MODES, @@ -552,7 +554,9 @@ impl ModelChoice { pub(crate) fn default_reasoning_mode(self) -> ReasoningMode { match self { - Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Pro => ReasoningMode::Low, + Self::DeepSeekV4Flash0731 | Self::DeepSeekV4FlashVision | Self::DeepSeekV4Pro => { + ReasoningMode::Low + } Self::Glm52 | Self::Glm53Flash => ReasoningMode::Max, Self::Qwen38FlashNext => ReasoningMode::Low, } diff --git a/tools/issue100-vision-reference.py b/tools/issue100-vision-reference.py new file mode 100644 index 0000000..f4bb6ff --- /dev/null +++ b/tools/issue100-vision-reference.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Reference-only check of Vision-Exp weights and the official PyTorch image graph. + +Production code never invokes this script. Inputs are the pinned DS4 source and +DeepSeek source revisions recorded in docs/issue100-vision, and native dumps +from the ignored issue100_encoder_real_weights test. +""" +import argparse +import importlib.util +import json +from pathlib import Path +import sys +from types import SimpleNamespace + +import numpy as np +import torch + +p = argparse.ArgumentParser() +p.add_argument('--ds4-source', type=Path, required=True) +p.add_argument('--official-source', type=Path, required=True) +p.add_argument('--artifacts', type=Path, required=True) +p.add_argument('--native', type=Path, required=True) +p.add_argument('--image', type=Path, required=True) +p.add_argument('--device', default='mps') +a = p.parse_args() +parser_path = a.ds4_source / 'gguf-tools/mixed/splice_mixed_expert_layers_gguf.py' +spec = importlib.util.spec_from_file_location('ds4_gguf', parser_path) +gguf = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = gguf +spec.loader.exec_module(gguf) +sys.path.insert(0, str(a.official_source / 'inference')) +from vision import ViT, Aligner +from image_processor import load_image, build_image_block + +config = json.loads((a.official_source / 'config.json').read_text()) +config['dim'] = config['hidden_size'] +args = SimpleNamespace(**config) +file = a.artifacts / 'DeepSeek-V4-Flash-Vision-Encoder.gguf' +gguf.GGML_QUANT_SIZES[30] = (1, 2, "BF16") # GGML BF16, raw source weights. +info = gguf.parse_gguf(file) +torch.set_default_dtype(torch.bfloat16) +torch.set_default_device(a.device) +vit, aligner = ViT(args), Aligner(args) +with file.open('rb') as f: + def state(prefix): + result = {} + for name, t in info.tensor_by_name.items(): + if not name.startswith(prefix): + continue + assert t.ggml_type == 30, (name, t.ggml_type) + f.seek(t.data_offset) + values = np.frombuffer(f.read(t.n_bytes), dtype=' .99, result +print(json.dumps(result), flush=True)