Skip to content

Add Rendering - #16

Draft
itsafuu wants to merge 67 commits into
mainfrom
dev_rendering
Draft

Add Rendering#16
itsafuu wants to merge 67 commits into
mainfrom
dev_rendering

Conversation

@itsafuu

@itsafuu itsafuu commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

itsafuu added 30 commits July 29, 2026 15:47
- 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
… 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
itsafuu added 30 commits August 6, 2026 17:42
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant