mirror of
https://github.com/storytold/craft-ai-integrations.git
synced 2026-11-02 18:06:49 +00:00
1a02afd6be
* refactor(engine-rten): let another engine reuse the SAM 2.1 request handling Loaded, Backend, Prediction, read_prompt, prompt_points and best_proposal become public. Visibility only: the click and box mapping, the picture kept between requests, the empty-mask answer and the size checks then live in one place for every SAM 2.1 engine. * feat(engine-burn): crate shell and the script that generates the SAM 2.1 architecture tools/burn/generate_sam21.py checks the two published ONNX files against the pinned size and SHA-256, runs the pinned burn-onnx 0.22.0 (an example of craft-ai-burn-setup), applies the five asserted fixes, drops the generated loaders that panic on a bad file, splits by submodule so every file is under 700 lines, formats and writes. --check proves the tree is what the script produces. REVISION must equal the saved-weights cache revision. Cargo.lock additions are Burn with wgpu and ndarray (generated). * feat(burn-setup): prepare SAM 2.1 weights for Burn once, on this computer Brings in craft-ai-burn-setup (feature `conversion`): checks the two published ONNX files by size and SHA-256, converts them to Burn weights with the pinned burn-onnx 0.22.0, and keeps one checked, locked, shared copy. Nothing is hosted or downloaded. Mutation group bp (10 cases). Cargo.lock additions are the optional burn-onnx tree (generated). * feat(engine-burn): GENERATED SAM 2.1 tiny architecture (30 files) Output of tools/burn/generate_sam21.py, revision sam21-tiny-burn-0.22.1-onnx-0.22.0-v1, from the published encoder.onnx and decoder.onnx with burn-onnx 0.22.0 and its five asserted fixes. Do not review line by line; rerun the script with --check instead. Longest file 699 lines. Marked linguist-generated. * feat(engine-burn): SAM 2.1 runner for image.segment on Burn Sam21Runner (engine burn, model sam21) over the generated architecture. The request handling, picture kept between requests, empty-mask answer and click/box mapping are the shared Loaded from craft-ai-engine-rten, so only the model run lives here. Weights load through BurnpackStore and come back as errors; every call into the generated code is guarded against panics. Graphics card when one answers, else the processor. An .onnx file is answered with 'prepare it first', not a guess. 16 unit tests on a fake backend: click and box mapping, empty mask, short mask, a failing model, shared conformance run with Cancel, damaged and truncated files. * test(engine-burn): live comparison with rten, and the shared revision check The ignored live test runs the real SAM 2.1 files through rten (published ONNX) and Burn (weights prepared on this computer) on the astronaut sample: one click and a box, best-mask IoU, timings, and a truncated prepared file refused. The revision test fails when the generator, the compiled architecture and the saved-weights cache key disagree. * test+docs(engine-burn): mutation group be, weight-loading tests and the Burn engine guide Group be: 15 cases, one target each, all caught (saved-weights marker, load completeness, panic guard, answer length, best proposal, file names, declared card makers, the revision shared by script, architecture and cache key, the catalogue route and the .bpk binding rule). fully_applied is split out so the load checks can be tested without the store's own refusals. Group nf's onnx case follows the rewritten extension check. docs/BURN-ENGINE.md: how to regenerate, the five fixes, the revision bump rule, limits. * feat(engine-burn): CRAFT_AI_BURN_DEVICE=cpu forces the processor, and the processor path is measured * feat(engine-burn): prepare the saved copy when loading from the downloaded ONNX files The host step: given the downloaded encoder.onnx with decoder.onnx beside it, the runner calls craft-ai-burn-setup (exact source bytes checked, reused when already saved) and loads from the burn-prepared folder beside the model folder. Behind the default 'prepare' feature; mutation group be gains two cases (17/17); docs say what is measured. * feat(client): optional burn feature and a catalogue route for SAM 2.1 on Burn craft-ai-client gains the cargo feature burn (ProviderFactory::standard() then knows the engine). SAM 2.1's recipe lists a burn NativeRoute after rten, with the same two published ONNX files (one download for both, nothing converted is hosted). rten stays first and the default. The binding accepts the prepared .bpk file. Engines now list rten, burn, candle; the tests that assumed the Burn slot had no files, or that no built-in engine exists whatever cargo features are on, say what they mean. burn_engine.rs runs two apps through the client with the real prepared files (ignored). * fix(burn-setup): sweep the scratch folders a killed run left, and keep craft-ai-features a dev dependency CodeRabbit on #89: tempdir_in(cache) folders hold the two source copies (about 130 MB) and were removed only by Drop, so a killed app leaked them for good. They now carry the cache key and a role in their names, and after the key's lock is taken the same key's leftovers are removed (other keys' folders, the lock file and links are left alone). Red first: leftovers_of_a_killed_run_are_swept_for_this_key_only. Mutation cases burn-killed-run-leftovers-kept and burn-sweep-takes-other-keys, both caught. craft-ai-features is only used by a unit test, so it moves to dev-dependencies. * fix(engine-burn): review findings on #90 CodeRabbit: the generator now refuses an unknown option (a mistyped --chek used to select the run that rewrites src/model; red first: tools/burn/test_generate_sam21.py failed to import parse_args); the preparation live test needs the prepare feature and is ignored by default; the refusal test no longer leaves burn-prepared/<key>.lock in the shared system temp folder (the model folder is nested one level inside the test's own folder; confirmed the old layout leaked it on this PC); docs, STATUS and the mutation README no longer describe the client feature and catalogue route, which arrive in the next PR, as available. * docs(client): say the Burn route and client feature exist, and what the status line really covers CodeRabbit on #91: the STATUS row said the host step was not built. Given the downloaded encoder.onnx the runner prepares the saved copy itself on first load; the row now says that, names the limits (no progress or Cancel during that load, no app run through the shared binding, no live run on macOS or Linux) and LOCAL-BURN-PREPARATION.md no longer calls engine registration separate work. Also restores the client feature and catalogue route in the docs and rustdoc that the #90 review fix had marked as planned, and the three mutation-README claims for them. * fix(burn-setup): sweep only the scratch folders, not a renamed copy of the key's folder CodeRabbit on #89 (second round): sweep_stale removed every folder starting with <key>., so a damaged copy a person renamed to <key>.backup would be deleted on the next preparation. It now matches only <key>.work., <key>.conv. and <key>.out., the names the scratch folders are made with (one constant for both). Red first: the sweep test gained a <key>.backup folder that must survive. Mutation group bp 13/13 caught, including the new case burn-sweep-takes-a-renamed-copy. * test(burn): the generator's usage errors must exit with code 1 CodeRabbit on #90 (second round): the parse_args tests asserted only SystemExit, which a SystemExit(0) would also satisfy. They now assert the code. Checked against a mutant that exits 0: 6 failures. * fix(engine-burn): a damaged saved copy says it is damaged and where it lives CodeRabbit on #90 (third round) pointed out that a saved copy with a bad manifest or weights fails the same way on every retry, behind the message 'could not be read or written. Try again.' It proposed deleting the copy. The rule that a derived copy is refused, never repaired or deleted for the person, stays (#89 and its tests pin it); the dead end was the wording. craft-ai-burn-setup now answers every unusable saved copy with DAMAGED_COPY (remove it, prepare again, no 'try again'), and the Burn engine adds where it lives: the burn-prepared folder beside the model folder. Tests written first (they did not compile without DAMAGED_COPY and explain): a damaged copy of three kinds gets that answer and is left untouched; the engine explains it and passes other errors on. Mutation: bp 2 new cases and be 1 new case, all caught. * fix(burn-setup): a missing or unreadable saved weight is a damaged copy too CodeRabbit on #90 (fourth round): hash_file(...)? on a saved encoder.bpk or decoder.bpk that is gone returned the generic 'try again' error before the damaged-copy branch could run. It now maps to DAMAGED_COPY. Red first: the damaged-copy test gained 'missing encoder' and 'missing decoder' cases and failed with the old message. New mutation case burn-missing-saved-weight-answered-with-try-again; the saved-hash case was re-aimed at the new line. bp 16/16 caught. * fix(burn-setup): a manifest that cannot be opened or read is a damaged copy too CodeRabbit on #91 (round 5): File::open and read_to_end on an existing manifest returned the generic 'try again' error, so the engine gave no deletion guidance. Both now map to DAMAGED_COPY. Red first: a_manifest_that_cannot_be_opened_is_a_damaged_copy (Windows: another handle holds it exclusively; Unix: mode 0, ending quietly where permissions are not enforced) failed with the old message. The Unix variant was not run here. New mutation case burn-unopenable-manifest-answered-with-try-again; bp 17/17 caught (Windows). * test(burn-setup): write the Unix permission as octal (clippy::non_octal_unix_permissions, CI only) --------- Co-authored-by: scratch <s@s>
31 lines
4.7 KiB
Python
31 lines
4.7 KiB
Python
"""Deliberate-bug cases for group `nf`: planning and offering a built-in runtime's own files.
|
|
|
|
Loaded by mutation_check.py. Each case breaks one rule and names the test that should notice.
|
|
Format: (name, file, old, new, package, test target, test filter).
|
|
"""
|
|
F = 'crates/ai-features/src/'
|
|
FE = 'craft-ai-features'
|
|
|
|
NF = [
|
|
('native-plan-ignores-usable', F + 'feature.rs', '.find(|r| r.usable && r.native.iter().any(|n| n.provider == provider))', '.find(|r| r.native.iter().any(|n| n.provider == provider))', FE, 'native_setup', 'cannot_run_yet'),
|
|
('engine-recipe-ignores-the-engine', F + 'feature.rs', '.find(|r| r.usable && r.native.iter().any(|n| n.provider == provider))', '.find(|r| r.usable)', FE, 'engine_recipe', 'each_engine_plans_from'),
|
|
('native-plan-takes-the-first-recipe', F + 'plan.rs', 'let recipe = feature.engine_recipe(provider);', 'let recipe = feature.first_usable_recipe();', FE, 'engine_recipe', 'each_engine_plans_from'),
|
|
('first-usable-recipe-takes-the-first', F + 'feature.rs', 'self.locals.iter().find(|r| r.usable)', 'self.locals.first()', FE, 'engine_recipe', 'plain_built_in_offer_skips'),
|
|
('native-offer-takes-the-first-recipe', F + 'setup.rs', 'Some(provider) => feature.engine_recipe(provider)?,', 'Some(_) => feature.locals.first()?,', FE, 'setup', 'unusable_first_recipe'),
|
|
('plain-built-in-offer-takes-the-first-recipe', F + 'setup.rs', 'None => feature.first_usable_recipe()?,', 'None => feature.locals.first()?,', FE, 'engine_recipe', 'plain_built_in_offer_skips'),
|
|
('native-option-from-every-recipe', F + 'feature.rs', 'if !out.contains(&route.provider.as_str()) {', 'if true {', FE, 'native_setup', 'first_usable_recipe'),
|
|
('native-option-from-the-first-recipe-only', F + 'options.rs', '.engine_recipe(provider)\n .and_then(|r| r.native.iter().find(|n| n.provider == provider))', '.first_usable_recipe()\n .and_then(|r| r.native.iter().find(|n| n.provider == provider))', FE, 'engine_recipe', 'each_engine_plans_from'),
|
|
('native-plan-takes-a-route-without-files', F + 'plan.rs', '.find(|n| n.provider == provider && !n.models.is_empty())\n }) else {\n return out;\n };\n fill_files(&mut out, &route.models, inventory);\n check_disk', '.find(|n| n.provider == provider)\n }) else {\n return out;\n };\n fill_files(&mut out, &route.models, inventory);\n check_disk', FE, 'native_setup', 'unfinished_or_unknown'),
|
|
('native-plan-takes-any-provider', F + 'plan.rs', '.find(|n| n.provider == provider && !n.models.is_empty())\n }) else {\n return out;\n };\n fill_files(&mut out, &route.models, inventory);\n check_disk', '.find(|n| !n.models.is_empty())\n }) else {\n return out;\n };\n fill_files(&mut out, &route.models, inventory);\n check_disk', FE, 'native_setup', 'unfinished_or_unknown'),
|
|
('native-plan-skips-the-disk-check', F + 'plan.rs', ' fill_files(&mut out, &route.models, inventory);\n check_disk(&mut out, inventory);', ' fill_files(&mut out, &route.models, inventory);', FE, 'native_setup', 'free_space'),
|
|
('native-offer-ignores-the-provider', F + 'setup.rs', 'selected_provider.is_none_or(|provider| n.provider == provider)', 'true', FE, 'setup', 'selected_provider'),
|
|
('built-in-offer-compares-comfyui-files', F + 'setup.rs', 'let compared = if let Some(native) = native {', 'let compared = if let Some(native) = native.filter(|_| false) {', FE, 'setup', 'needs_only_the_model'),
|
|
('option-offered-for-a-route-without-files', F + 'options.rs', ' .filter(|n| !n.models.is_empty())\n else {', ' else {', FE, 'native_setup', 'not_unbuilt_burn'),
|
|
('native-options-before-later-locals', F + 'options.rs', 'locals.extend(natives);', 'locals.splice(0..0, natives);', FE, 'native_setup', 'follow_every_local'),
|
|
('built-in-offer-uses-the-comfyui-terms', F + 'setup.rs', 'agreement: agreement_for(route),', 'agreement: agreement(),', FE, 'setup', 'agrees_to_its_own'),
|
|
('native-option-loses-its-model-id', F + 'options.rs', 'model: native.model.clone(),', 'model: String::new(),', FE, 'native_setup', 'candle_with_its_own_size'),
|
|
('onnx-is-not-weights', F + 'recipe.rs', ' name.ends_with(".safetensors") || name.ends_with(".onnx")', ' name.ends_with(".safetensors")', FE, 'native_onnx', 'an_onnx_file'),
|
|
('onnx-name-is-case-sensitive', F + 'recipe.rs', 'let name = m.filename.to_ascii_lowercase();', 'let name = m.filename.clone();', FE, 'native_onnx', 'an_onnx_file'),
|
|
('onnx-path-refused-when-binding', 'crates/ai-client/src/local.rs', '["safetensors", "onnx", "bpk"]', '["safetensors", "bpk"]', 'craft-ai-client', 'local_native', 'the_native_route_is_registered'),
|
|
]
|