Add Rendering - #16
Draft
itsafuu wants to merge 67 commits into
Draft
Conversation
- New include/processingbase/vulkan_init_guard.hpp with a process-wide, header-declared magic-static mutex accessor sgns::sgprocessing::VulkanInitMutex() - Serves as the single synchronization primitive for all Vulkan instance/device-creation call sites in this process (MNN's 3 existing sites plus RenderProcessor's future site)
- Remove the file-scoped, function-local static mnn_vulkan_mutex from MNN_Image::Process() - Acquire sgns::sgprocessing::VulkanInitMutex() at the same point in the function body (top of Process()), preserving the same lock span
- Both createSession(MNN_FORWARD_VULKAN) call sites previously had zero synchronization, unlike MNN_Image's now-shared guard - Wrap each createSession call in a scope guarded by sgns::sgprocessing::VulkanInitMutex(), hoisting the MNN::Session* declaration outside the lock scope so the existing !session failure check is unaffected
…ext, vk-bootstrap init, deterministic device selection
…h, require shader for RENDER passes
… backend - Flip config.type from MNN_FORWARD_CPU to MNN_FORWARD_VULKAN in 4 processor files - Wrap createSession() calls in shared VulkanInitMutex() lock-guard, matching the already-migrated string/image/volume pattern - Downstream tensor-copy logic, numThread, backendConfig left unchanged
… backend - Flip config.type from MNN_FORWARD_CPU to MNN_FORWARD_VULKAN in 4 processor files - Wrap createSession() calls in shared VulkanInitMutex() lock-guard - Each file's own nullptr failure-return convention preserved unchanged
…ssors to Vulkan backend
- Flip config.type from MNN_FORWARD_CPU to MNN_FORWARD_VULKAN across 5 files
- Wrap createSession() calls in shared VulkanInitMutex() lock-guard
- texturecube.cpp has two independent call sites; each gets its own
lock-guard scope, single shared include added once
- Each file's own failure-return convention (nullptr / ProcessingResult{})
preserved unchanged
…he guarded pattern, not a stale count
- Removes the stale, undercounting call-site count ("MNN's 3 existing... sites")
- Describes the guarded set as a pattern instead: every MNN Vulkan-backend
createSession() call site, plus RenderProcessor's lazy-init path
- Points readers at 'grep MNN_FORWARD_VULKAN src/processors/*.cpp' for the
current authoritative count rather than trusting a comment that can drift
- Mutex implementation (VulkanInitMutex()) is byte-identical, unchanged
- Fix pre-existing JSON syntax error in shader_config.type (missing comma, trailing comma) that made the schema file invalid JSON - Narrow shader-language enum to glsl/spirv only via new shared shader_source_type definition (drops hlsl/metal entirely, D-10) - Add render_shader_config + shader_stage (multi-stage vertex+fragment shader pipeline, D-11/D-12) - Add render_target (all-required framebuffer config, D-15) - Add vertex_layout_entry + vertex_buffer (D-16/D-16 Amendment) and index_buffer (D-17) - Add pipeline_state (curated topology/cull/winding/depth-test subset, D-13/D-14) - pass gains six new optional render-only properties; pass.allOf split into separate compute/render conditional branches (documentation only - quicktype does not enforce allOf/if/then required)
- Full quicktype regeneration (--source-style multi-source rewrites every currently-referenced type's header) from the fixed/extended gnus-processing-schema.json - New headers: RenderShaderConfig, ShaderStage, RenderTarget, VertexLayoutEntry, VertexBuffer, IndexBuffer, PipelineState, ShaderSourceType, RenderShaderUniform, ShaderUniform (renamed from Uniform), Stage, Topology, CullMode, FrontFace, DepthTest, ColorFormat, DepthFormat, VertexLayoutFormat, IndexType - Pass.hpp gains boost::optional accessors: get_/set_render_shader(), get_/set_render_target(), get_/set_vertex_layout(), get_/set_vertex_buffer(), get_/set_index_buffer(), get_/set_pipeline_state() - RenderTarget's six fields and VertexBuffer's source are plain (non-optional) required members, confirmed via generated output - Deleted orphaned generated/ShaderType.hpp and generated/Uniform.hpp (superseded, zero references outside generated/ confirmed before deletion)
- Adds sgns::sgprocessing::ShaderCompiler with CompileAndValidate(), a Vulkan-device-free component (zero VkInstance/VkDevice/VkPhysicalDevice) that compiles job-supplied GLSL to SPIR-V via shaderc and unconditionally validates all SPIR-V (compiled or directly-submitted) via SPIRV-Tools before it can ever reach vkCreateShaderModule. - Both the GLSL-compiled path and the direct-SPIR-V path call spvtools::SpirvTools::Validate() explicitly -- shaderc's CompileGlslToSpv() success does not imply SPIRV-Tools validation. - New standalone SGShaderCompiler CMake target linking shaderc::shaderc/SPIRV-Tools::SPIRV-Tools, wired into src/CMakeLists.txt.
…r validity checks - Add Error::SHADER_COMPILE_FAILED/SPIRV_VALIDATION_FAILED, wired into the OUTCOME_CPP_DEFINE_CATEGORY_3 switch - Init()'s JSON-parsing catch broadened to also catch std::exception, closing the newly-live crash vector from quicktype's narrowed ShaderSourceType enum's from_json (throws plain std::runtime_error, not nlohmann::json::exception) - CheckProcessValidity()'s PassType::RENDER branch now checks render_shader/ render_target/vertex_buffer/vertex_layout presence (replacing the obsolete get_shader() check, which is compute-only after plan 02-01) - GetCidForProc() extended: for render passes, fetches each render_shader stage's source (queued alongside the existing image fetch, single ioc->run() unchanged), then runs every stage through ShaderCompiler::CompileAndValidate() before mainbuffers->first is populated via a new SerializeCompiledStages() helper (provisional wire format, documented inline for Phase 3's RenderProcessor to consume/revise) - src/processingbase/CMakeLists.txt links SGShaderCompiler Verified via a real MSVC /Zs syntax+semantic check against the project's actual include paths (full link build blocked by pre-existing missing vendored shaderc/SPIRV-Tools/vk-bootstrap installs in this session, same constraint documented in 02-03-SUMMARY.md).
…ped shader compilation get_render_shader() returns boost::optional<RenderShaderConfig> by value (quicktype's standard convention). Binding `stages` as a reference through a chained .value().get_stages() call left it pointing at a temporary that was destroyed at the end of the statement, so the render-stage loop always iterated zero times. This meant shader compile/validate was never actually reached during dispatch, and the malformed-GLSL/invalid-SPIR-V rejection tests silently fell through to an unrelated missing-input error instead of exercising the SHADER_COMPILE_FAILED/SPIRV_VALIDATION_FAILED paths. Copy the optional into a named local first so its lifetime covers the loop. Found via real build+test verification (not caught by code review or the isolated MinGW spike used during planning, since neither actually ran the project's own MSVC toolchain against the real dispatch path).
- ProcessingResult gains a new optional error field (ProcessingErrorStage enum + ProcessingError struct, D-25/D-26) carrying per-stage VkResult/ context detail without changing StartProcessing()'s signature - ProcessingManager::Error gains PROCESSING_FAILED = 9 for the dispatch gate Task 2 will add
…oint - ProcessingManager::Process() now checks processResult.error / hash.empty() immediately after StartProcessing() returns and skips FileManager::SaveASync entirely on failure, returning Error::PROCESSING_FAILED (D-27/D-28). Covers both the render path (new error field) and the existing MNN path (pre-existing empty-hash-on-failure sentinel) with a single gate -- zero changes needed to any of the 15 MNN processor files. - SerializeCompiledStages()/its GetCidForProc() call site now carry each stage's real entry_point string (length-prefixed UTF-8) instead of dropping it, closing the SPIR-V wire-format gap RESEARCH.md's Pitfall 6 flagged for plan 03-03's RenderProcessor parser.
…nder-pass config Extends GetCidForProc()'s render branch to fetch vertex_buffer/index_buffer as independently-named "input:" references (not the coincidental single model-index input the current fixture happens to reuse), and packs render_target/pipeline_state/vertex_layout/uniforms/data_transform_count alongside the vertex/index bytes into a new SerializeRenderPassConfig() wire format -- the only channel this Pass-level data has to reach RenderProcessor under StartProcessing()'s fixed signature (D-25). - New anonymous-namespace SerializeRenderPassConfig() helper, wire format documented in the function's header comment - GetCidForProc()'s isRender branch now independently resolves vertex_buffer/ index_buffer sources via the existing GetInputIndex()/m_inputMap mechanism - The old unconditional GetSubCidForProc(ioc, imageUrl, mainbuffers->second) fetch is now guarded by if (!isRender), since mainbuffers->second is populated by SerializeRenderPassConfig() for render passes instead - Preserves the pre-existing INPUT_UNAVAIL failure semantics via an explicit vertexBuffer->empty() check, since mainbuffers->second is no longer ever empty for a render pass regardless of fetch success
…er passes Adds defensive Create()-time rejection of vertex_buffer/index_buffer/uniform source strings this phase has no real resolution path for, closing T-03-02-01/T-03-02-02 from the plan's threat register. - vertex_buffer.source / index_buffer.source (when index_buffer's source is present) must start with "input:" -- output:/internal:/parameter: sources fail with a clear, documented reason (no cross-pass dependency graph exists, no parameter:-sourced raw-buffer codec exists) - Each uniform declaring a source must use the "parameter:" prefix (Pitfall 8 -- the schema itself does not constrain this string); a uniform with neither a source nor a usable value also fails cleanly - Both checks share a single log-message lambda so the vertex_buffer/ index_buffer message text is not duplicated in source
…ssor - ParseCompiledStages()/ParseRenderPassConfig() invert plans 03-01/03-02's wire formats byte-for-byte, bounds-checking every read against buffer size so a malformed/truncated buffer returns a RESOURCE_RESOLUTION error instead of reading out-of-bounds. - ParseRenderPassConfig() is the only method that reconstructs real sgns::RenderTarget/PipelineState/VertexLayoutEntry/uniform-map instances inside RenderProcessor, and the only source of data_transform_count. - ResolveUniforms() resolves each uniform's literal value or parameter:-sourced value, packs bytes per declared DataType at a 16-byte-aligned offset per uniform (std430-avoidance per D-29/D-30), and applies the fixed 128-byte push-constant/descriptor-set threshold. - MakeError() constructs a structured ProcessingResult error (D-25/D-26).
… + ordered teardown - CreateBufferDedicated()/CreateImageDedicated() each perform exactly one vkAllocateMemory call, sized to the object's own memory requirements (D-18/D-19, no sub-allocation), and register their teardown via PushTeardown() on success. A failed vkAllocateMemory/vkBind*Memory destroys the just-created buffer/image before returning the error (D-24), since it isn't registered on m_teardown yet. - CheckFormatSupport() queries vkGetPhysicalDeviceFormatProperties and fails with a structured FORMAT_UNSUPPORTED error naming the specific format (RESEARCH.md Pitfall 7) rather than letting image/render-pass creation fail with an opaque VkResult. - PushTeardown()/RunTeardown() implement the single ordered-teardown stack (D-22/D-24) every later plan in this phase reuses -- unwinds in reverse order via rbegin()/rend(). - No new code in this task takes VulkanInitMutex() -- confirmed the lock remains scoped to InitializeContext()'s existing instance/device creation only, per RESEARCH.md's anti-pattern warning.
…it clears) - BuildRenderPass(): bounds-checks render_target width/height against a new kMaxRenderDimension (8192), format-support-checks color/depth via plan 03-03's CheckFormatSupport(), then creates a VkRenderPass with explicit VK_ATTACHMENT_LOAD_OP_CLEAR on both color/depth attachments (never DONT_CARE except the unused stencil aspect), VK_SAMPLE_COUNT_1_BIT unconditionally per DETV-02, and the color attachment's finalLayout set to VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL for plan 03-05's readback. - BuildFramebuffer(): allocates dedicated color+depth VkImage/VkImageView pairs via plan 03-03's CreateImageDedicated() (DEVICE_LOCAL) and builds the VkFramebuffer referencing the render pass. - New ToVkFormat(ColorFormat)/ToVkFormat(DepthFormat) schema-to-Vulkan mapping helpers. - Every created object registers teardown via PushTeardown() in creation order (D-22/D-24).
…ant/descriptor-set layout) - BuildPipeline(): one VkShaderModule + VkPipelineShaderStageCreateInfo per parsed shader stage, using each stage's real entry_point (plan 03-01), never a hard-coded "main"; per-stage module-creation failure is isolated via SHADER_MODULE_CREATION and cannot leak an earlier stage's module (D-24) since teardown is registered immediately after each successful vkCreateShaderModule call. - Vertex input binding/attributes auto-computed from vertex_layout's scalar-component reading (stride = sum of per-entry scalar byte sizes, location = array index) via new ToVkFormat(VertexLayoutFormat)/ VertexFormatByteSize() helpers. - Fixed (never VK_DYNAMIC_STATE_*) topology/cull-mode/front-face/depth-test baked from pipeline_state (or schema defaults) via new ToVkTopology()/ToVkCullMode()/ToVkFrontFace()/ToVkBool() helpers; depthCompareOp fixed at VK_COMPARE_OP_LESS (D-14); multisample rasterizationSamples fixed at VK_SAMPLE_COUNT_1_BIT (DETV-02); viewport/ scissor sized from plan 03-04 Task 1's validated render-target dimensions. - Pipeline layout branches on D-29/D-30's fixed 128-byte push-constant threshold: push-constant range when uniforms fit and are non-empty, a single descriptor-set-layout/pool/set (maxSets=1) UBO when they don't, zero of both when no uniforms are declared. - Every created object (shader modules, descriptor set layout/pool, pipeline layout, pipeline) registers teardown via PushTeardown() in creation order (D-22/D-24).
Adds RenderProcessor::UploadBuffers()/RecordAndSubmit()/Readback()/ ColorFormatByteSize() -- validates vertex/index buffer byte lengths against the pipeline's computed stride/index-type BEFORE any draw call is recorded (closes T-03-03-02), uploads vertex/index/uniform bytes into dedicated HOST_VISIBLE|HOST_COHERENT buffers with direct vkMapMemory/memcpy (D-20/D-21, no manual flush), and records+submits a single command buffer (bind pipeline/buffers, push-constants or descriptor-set bind, draw(Indexed), the vkCmdCopyImageToBuffer readback copy recorded inline before vkEndCommandBuffer per Pitfall 4 -- no extra layout-transition barrier needed since the color attachment's finalLayout is already VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) plus a synchronous vkDeviceWaitIdle (D-23). StartProcessing() is intentionally still the pre-existing stub -- wiring these methods together is Task 2's job.
…orm stance Fully rewrites RenderProcessor::StartProcessing(), replacing the stub's hard-coded zero-hash return with the real call sequence: ParseCompiledStages() -> ParseRenderPassConfig() -> ResolveUniforms() -> BuildRenderPass() -> BuildFramebuffer() -> BuildPipeline() -> UploadBuffers() -> data_transform gate -> RecordAndSubmit() -> Readback() -> sha256(readback bytes) -> ProcessingResult. Every exit path (success and every intermediate failure) calls RunTeardown() before returning, so no per-job Vulkan object is ever leaked (D-22/D-24) -- including the RENDER-07 data_transform gate: absent/ empty data_transforms is a no-op passthrough, any non-empty data_transforms fails cleanly with a structured DATA_TRANSFORM_UNSUPPORTED error (no executor exists anywhere in this codebase, per RESEARCH.md Pitfall 9). Satisfies RENDER-01/03/06/07/08/09 and DETV-02's code-level guards -- the only remaining phase work is DETV-01's same-node repeat-run determinism proof (plan 03-06).
…rProcessor InitializeContext()'s PhysicalDeviceSelector left require_present at its default (true), which rejects every physical device with no_surface_provided since RenderProcessor never creates a VkSurfaceKHR (headless/offscreen, no swapchain -- CTX-01/D-23). This was never exercised until this plan's happy-path test became the first fixture to actually reach InitializeContext() with a real, fetchable render pass (all prior fixtures failed earlier, at the fetch stage, before dispatch ever reached StartProcessing()). Disabling require_present is the correct fix for a headless renderer with no presentation surface.
…visibility - Move RenderProcessor::IsAcceptable from private to public so vulkan_gpu_probe.cpp can call it directly instead of duplicating the DISCRETE_GPU/INTEGRATED_GPU filter - Add sgns::sgprocessing::HasUsableVulkanDevice(): builds a throwaway VkInstance under the shared VulkanInitMutex(), enumerates devices headlessly (require_present(false)), filters via RenderProcessor::IsAcceptable, tears down the instance on every path, never throws - Register vulkan_gpu_probe.cpp/.hpp in SGProcessors's CMake source list
…parse Init() - CheckProcessValidity(): INFERENCE case now returns MODEL_MISSING (not PROCESS_INFO_MISSING) when model is absent, and adds a new explicit ModelFormat::MNN executability check returning MODEL_FORMAT_UNSUPPORTED for recognized-but-non-MNN formats (e.g. ONNX) that parse successfully but aren't executable - CheckProcessValidity(): RENDER case's missing-render_shader branch now returns RENDER_SHADER_MISSING (other RENDER checks left unchanged) - Init(): new pre-parse raw-JSON scan runs after nlohmann::json::parse() and before sgns::from_json(), catching unrecognized passes[].type and passes[].model.format strings before the quicktype-generated from_json throws a context-free std::runtime_error, returning UNKNOWN_PASS_TYPE / MODEL_FORMAT_UNSUPPORTED with the offending value named in the log - All accesses guarded with is_object()/is_array()/is_string()/contains() before dereferencing; malformed/absent passes fall through unchanged to the existing sgns::from_json()/catch path (T-09-21 mitigation)
…ss() calls
- Brace-initialize ProcessOutput output{} so all ExecutionManifest char[]/uint8_t[]
fields without a default member initializer are zero-initialized instead of
holding indeterminate stack memory that leaked into the hashed serialization
- Compute the manifest self-hash over a timing-zeroed copy (hashInput) instead
of the live manifest, excluding startTimeUsec/endTimeUsec/wallClockUsec from
combinedHash/manifest.manifestHash while keeping real wall-clock values in
the returned manifest for provenance (ARTF-04)
…rashing
ProcessOutput's artifact-metadata builder called procInput.get_format().value()
unconditionally, but BUFFER-type inputs (e.g. a render pass's vertex_buffer
source) may legitimately omit the "format" field -- CheckProcessValidity()
already defaults this case to INT8 with a warning. Every prior test only
exercised Process() with an explicit-format input, so this crash
("uninitialized optional") was latent until 09-11 Task 2 wired
RenderConformanceTest to actually call Process() through the real Vulkan
pipeline for the first time.
- src/processingbase/ProcessingManager.cpp: value_or(INT8) mirroring the
existing BUFFER-type default convention
- New 5-arg Process(ioc, chunkhashes, model, output_locations, execCtx) overload - Both overloads delegate to shared private ProcessInternal() - Schema-derived gpuMemoryBudget/maxOutputArtifactBytes/deadlineMs/progressCallback now apply only when the caller's field is still unset (0/empty), so an explicit caller-supplied value from the new overload is never overwritten - Legacy 4-arg overload behavior unchanged (fresh ExecutionContext every field starts 0)
…kipped unit tests CancelMidRenderPass and CancelMidMNNInference's GTEST_SKIP() reason strings now name the real full-pipeline coverage that closes this exact gap: Plan 09-12's RenderCancelBeforeStartProducesNoSuccessfulResult / CancelBeforeStartProducesNoSuccessfulResult in SuperGenius/test/src/processing_conformance_cancellation/cancellation_conformance_test.cpp. Per D-04, this file stays fixture-free — no fixture files added, GTEST_SKIP() calls unchanged, CancelBeforeStart unmodified.
…ap 5)
Root cause: StartProcessing()/Process() hardcoded maxLength=128 for every
job, but the tiny embedding model's fully-connected layer is compiled for
a fixed sequence length of 16 -- resizing its single input to anything
else broke MNN's internal shape inference ("Reshape error", "Compute
Shape Error", "Can't run session because not resized").
Fix: read the job's schema-declared "maxLength" parameter (16 for the
tiny embedding model, 128 for the legacy multi-input BERT model) instead
of a single hardcoded literal, and resize whenever a tensor's current
size doesn't already match that value (elementSize() != maxLength)
instead of the old arbitrary <=4 threshold. This generalizes correctly
to both models without touching any other processor file.
Also hardens runSession()'s previously-discarded MNN::ErrorCode return:
a failed session now returns an empty sentinel Tensor instead of letting
the caller read output data from an unresized/garbage session, and
StartProcessing() converts that sentinel into a structured
ProcessingResult.error instead of dereferencing a degenerate tensor.
Deviation from 09-13-PLAN.md: the plan's literal instruction was to
derive the per-call resize length from the ACTUAL parsed token count
(tokenIds.size()), on the assumption the conformance fixture's input
text tokenizes to exactly 16 tokens (matching the tiny model's fixed
shape). Empirically, StringConformanceProcessingTest's actual
test_input.txt fixture (shared with the legacy StringInputProcessingTest)
tokenizes to 12 tokens, not 16 -- so a token-count-driven resize breaks
the fixed-shape tiny model regardless (12*8=96 flattened features != the
FC layer's fixed 128). Deriving the resize length from the job's already
-present schema "maxLength" parameter instead satisfies the plan's actual
must_haves/acceptance criteria (both string tests pass, no other MNN
processor file touched) and follows the same find-param-by-name pattern
already used for "tokenizerMode"/"vocabUri" in ProcessingManager.cpp.
…n message - Add PassTypeToString() helper to anonymous namespace (switch over all 5 generated PassType values, raw-int fallback for future unhandled values) - ListAvailablePassTypes() and CanExecute()'s PASS_TYPE rejection message now include the name alongside the raw int - RejectUnregisteredPassType test simplified to assert on the human-readable name only, removing the stale numeric assumption (PassType::INFERENCE == 1) that broke when quicktype's alphabetized enum made it == 2
- Add QuantizeFloatBuffer/QuantizeByteBuffer no-op stub functions in new sgns::sgprocmanagerquant namespace, mirroring sgprocmanagersha's library shape - Add sgprocmanagerquant CMake target (zero third-party deps) and link it into SGProcessors so all 14 processor files can call it
- Add rawOutputCapture std::function field (quantized bytes, pre-quantize bytes), mirroring progressCallback's opt-in injection pattern via ExecutionContext - NoOp() deliberately leaves it unset, unlike progressCallback, so production/no-op callers pay zero capture-path cost; documented inline to prevent a future "fix" toward parity - Add missing <vector> include needed by the new field's signature
…ed-combined hash sites - Insert locally-owned copy + QuantizeFloatBuffer + rawOutputCapture guard at each file's per-chunk hash call site, without mutating MNN-owned data in place - Insert pre-quantize snapshot + in-place QuantizeFloatBuffer + rawOutputCapture guard at each file's stitched-combined hash call site (stitchedOutput is locally owned) - Add #include "util/quantization.hpp" to all 3 files
…ched-combined hash sites - Insert locally-owned copy + QuantizeFloatBuffer + rawOutputCapture guard at each file's per-chunk hash call site, without mutating MNN-owned data in place - Insert pre-quantize snapshot + in-place QuantizeFloatBuffer + rawOutputCapture guard at each file's stitched-combined hash call site (stitchedOutput is locally owned) - Add #include "util/quantization.hpp" to all 3 files - Verified SGProcessors target builds cleanly (build/Windows/Debug, MSBuild)
…h sites - Insert locally-owned std::vector<float> copy + QuantizeFloatBuffer + rawOutputCapture guard before each file's chunk-hash sha256 call - Redirect chunk-hash first argument from raw MNN data pointer to the quantized local copy - Leave rolling combined-hash call sites untouched (hash-of-hashes, not re-quantizable)
…nk-hash sites - Insert locally-owned std::vector<float> copy + QuantizeFloatBuffer + rawOutputCapture guard before each file's chunk-hash sha256 call - Redirect chunk-hash first argument from raw MNN data pointer to the quantized local copy - Leave rolling combined-hash call sites untouched (hash-of-hashes, not re-quantizable)
…sites - Insert per-branch locally-owned std::vector<float> copy + QuantizeFloatBuffer + rawOutputCapture guard before each of the two independent chunk-hash sha256 calls - Redirect each branch's chunk-hash first argument to its own quantized local copy - Leave both rolling combined-hash call sites untouched (hash-of-hashes, not re-quantizable)
- Insert QuantizeByteBuffer + rawOutputCapture before the single combined-hash call in RenderProcessor::StartProcessing - readbackBytes is locally-owned, so quantization mutates it in place (no copy-before-mutate constraint, unlike MNN tensor memory) - Phase 10 CAPT-02
….cpp) - New sgns::sgproccapture namespace: CaptureRecord, CaptureFile, SerializeCaptureFile, DeserializeCaptureFile - Reuses SerializeArtifact/SerializeManifest unmodified for the metadata+hash portion (D-01); appends a new length-prefixed raw-bytes section per rawOutputCapture invocation - DeserializeCaptureFile validates every declared length/count against remaining buffer size and a 1 GiB cap before allocating (T-10-02), and returns false (never throws) on malformed/truncated/oversized input (T-10-01a) - Round-trip serialize/deserialize and truncation/oversized-length rejection verified via a standalone scratch build (1 artifact, 2 chunk-hash-count, 2 capture records) - Phase 10 CAPT-01, CAPT-02
- New tools/ and tools/capture/ CMake subdirectories, wired via add_subdirectory(tools) in SGProcessingManager/CMakeLists.txt - New sgproccapture static library wrapping capture_file_format.hpp/.cpp (Plan 10-04), linked against sgprocmanagersha + SGArtifacts - New capture_harness executable: runs a Phase 09 fixture --repeat N times via ProcessingManager::Process()'s 5-arg ExecutionContext overload, independently re-hashes every captured buffer against the paired chunk/combined hash (CAPT-02 self-check), verifies same-node stability across all N runs (CAPT-03/D-04/D-05), and writes one machine/fixture/ timestamp-named .cap file only when both checks pass - Neither target is CTest-gated (Pattern 5) -- a meaningful cross-machine pass/fail needs Phase 11's physical machines
- New capture_diff executable: reads two .cap files, reports DIFF-01/02 per-element numeric divergence (absolute delta, relative delta, ULP distance, whole-buffer max/percentage-exceeding-threshold stats) over the final CaptureRecord's quantizedBytes, plus DIFF-03 hash-match booleans (contentHash/chunkHashes/combinedHash) computed independently from artifact/manifest metadata, to both console and a JSON report - Fixed named thresholds per D-07 (not CLI-configurable this phase): kRelativeDeltaEpsilonFloor (1e-6f), kDefaultFloatRelativeThreshold (1e-4), kDefaultByteAbsoluteThreshold (1) - Not CTest-gated (Pattern 5); links against Plan 10-05 Task 1's sgproccapture library
- Add guarded add_subdirectory(test) to SGProcessingManager/CMakeLists.txt under if(BUILD_TESTING), after the existing add_subdirectory(src) - Add add_subdirectory(capture) to test/CMakeLists.txt alongside the existing capability/execution/artifacts subdirectories
- test/capture/CMakeLists.txt: add_executable(capture_smoke_test) linked against SGProcessors (HasUsableVulkanDevice) + sgproccapture (DeserializeCaptureFile), registered via add_test(NAME CaptureSmokeTest) - test/capture/capture_smoke_test.cpp: runs capture_harness as a subprocess against the mnn-float fixture, asserts exit 0, exactly one well-formed output .cap file, and a successful DeserializeCaptureFile round-trip (artifacts.size()==1, combinedHash.size()==32) -- deliberately does not assert any specific hash value or cross-machine equality (Phase 11's job) - GTEST_SKIP()s (not fails) when HasUsableVulkanDevice() reports no usable GPU on the host, mirroring the existing Phase 09 conformance-suite convention - Verified: cmake configure + build succeeds; ctest -R CaptureSmokeTest passes (7.21s, real Vulkan device present on this host)
Repeated local/CI runs against the same OUTPUT_DIR accumulated prior runs' timestamped .cap files (D-02), so the 'exactly one file' assertion matched all of them instead of just this run's. Clean matching files before invoking capture_harness so the test is idempotent across reruns. Found during Phase 10 regression-gate re-verification.
…unit tests - QuantizeFloatBuffer: IEEE-754 canonicalization (denormal/NaN/Inf/signed-zero, D-06/D-07/D-08/D-09) followed by fixed-point scale-round-cast at S=2^20 (D-03/D-05), cited against Phase 11's measured Mac-vs-Windows divergence - QuantizeByteBuffer stays byte-identity for the render path, now documented as a deliberate Phase-11-data-justified decision, not an inherited stub - New quantization_test.cpp (CTest QuantizationTest) with 7 TEST_F cases covering every <behavior> bullet, exact bit-pattern comparisons only - New test/util/ CMakeLists.txt mirrors test/artifacts/'s shape; wired into test/CMakeLists.txt via add_subdirectory(util)
Phase 13 gap-closure attempt for VALD-01's MNN cross-hardware hash divergence: the original S=2^20 grid step gave only a ~9x margin over Phase 11's measured cross-machine maxAbsDelta and Phase 13's fresh re-validation showed 12/15 MNN chunk hashes still diverging. A local binary search over power-of-two S values against Secv01CounterTest.MnnCorruptedModelStillDiverges found S=2^14 (the plan's originally-proposed 64x-wider value) regresses SECV-01 deterministically -- the corrupted-model fixture's artifactId collides bit-for-bit with the correct model's at that grid coarseness. S=2^15 is the widest power-of-two grid step confirmed safe (one full power-of-two step of margin above the S=2^14 failure boundary), giving 32x the old grid step (~292x Phase 11's original maxAbsDelta) while QuantizationTest (7/7) and both SECV-01 cases still pass locally.
- Add chunkStats loop numeric-diffing rawRecordsPerArtifact[0][j] for each chunk (j < chunkHashCount), reusing ComputeFloat32Diff/ComputeUint8Diff unmodified - Add bounds-guarded fallback (sizeMismatch=true + stderr warning) for malformed/truncated capture files missing a chunk's raw record - Add chunkDiffs JSON array (index-aligned with chunkHashesMatch) and matching console output lines - Update header and inline doc comments to note the extension, preserving original trailing-record-only pass description as historically accurate - Purely additive: no pre-existing top-level JSON field renamed/removed
…FP16-opportunism hypothesis BackendConfig was previously unset (nullptr), leaving MNN at its default Precision_Normal, which permits GPU backends to opportunistically use FP16 for intermediate ops even on FP32-declared tensors. Different Vulkan implementations (Mac vs Windows) may make different FP16-vs-FP32 choices under that default, which is a plausible source of the cross-hardware divergence characterized in Plan 13-06 (chunk 10, exactly one S=2^15 grid step). This sets backendConfig.precision = Precision_High to force FP32 throughout and test whether that reduces or closes the divergence. Experimental -- not yet validated by a fresh cross-machine capture. Scoped to processing_processor_mnn_float.cpp only (the processor used by the float32 fixture under investigation); the other 6 MNN processors are untouched pending this experiment's outcome.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.