Skip to content

2026-09-04 Factory RPE histo inference batch

Queued the open RPE histo preprocessing cases and submitted one 25-case Factory workflow.

Work Done

  • Read the Factory API schema at https://inference.fra.virdx.dev/openapi.json and queried GET /rules/histo-preprocessing-rpe?cached=false.
  • Found 3,639 actionable cases and no cases already in flight.
  • Saved the actionable case objects to apps/factory/histo-preprocessing-rpe-open-cases.json in the mono checkout.
  • Confirmed from apps/factory/src/factory/services/histo_preprocessing/service.py that histo preprocessing dispatch uses SLIDES_PER_JOB = 25; a 256-case sync would therefore create 11 Argo workflows rather than one.
  • Submitted the first 25 case UIDs through POST /rules/histo-preprocessing-rpe/sync.
  • Factory created workflow factory-histo-preprocessing-jsc86: https://argo-workflows.fra.virdx.dev/workflows/argo-workflows/factory-histo-preprocessing-jsc86.
  • Verified all 25 selected cases appeared in the rule’s live in_flight map, then removed them from the saved queue. The queue now contains 3,614 cases.
  • Inspected live scheduling controls before considering the remaining backlog. The submitter has an Argo semaphore of 10, but its Kueue ClusterQueue has non-borrowable quotas of 2 GPUs, 96 CPUs, and 512 GiB memory. Since each RPE workflow requests 1 GPU, 4 CPUs, 64 GiB memory, and a 1 TiB local cache, Kueue can admit at most two concurrently. The namespace allows 500 Workflow objects and was using 147.
  • Filtered the saved queue to 673 scans whose identifiers start with histoscan/FR and end with /whole. Refreshed the rule state and confirmed all 673 were still actionable.
  • Manually created 27 Factory-compatible workflows under the inference API service-account identity: 26 workflows with 25 cases and one with 23. Their payload, deployed RPE config, image, resource requests, labels, annotations, and queue identity mirror an API-created workflow.
  • Added the supported virdx.dev/gpu-class: rtx-6000 label, which admission propagates to pods and converts to the hard NVIDIA-RTX-6000-Ada-Generation node selector. Verified the first new pod ran on virdx-compute-002, Kueue held total inference usage to two GPUs, and all 673 selected cases appeared in Factory’s live in-flight map.
  • Removed the 673 submitted cases from the local queue. It then contained 2,941 cases and no remaining FR.../whole cases.
  • Follow-up showed the first eight admitted Ada workflows all failed with main: OOMKilled (exit code 137). Their main containers had matching 64 GiB memory requests and limits. Failure times ranged from about 8 minutes to several hours. No selected case was materialized because the runner uploads and registers prepared outputs only after processing the full batch.
  • Terminated the remaining 19 workflows before their pending pods could run. A fresh uncached rule evaluation showed all 673 selected cases still missing and none in flight, so they were restored to the local queue. It contained 3,614 cases again.
  • Recovered per-scan preprocessing.log files by mounting the retained generic-ephemeral cache PVCs read-only. All eight failed FR workflows reached Visualizing results after component processing and cleanup, then OOM-killed before saving the first QC PNG. The earlier LE workflow completed ten scans and OOM-killed while transforming a 243426 × 97119 component on its eleventh scan.
  • Deleted the nine terminal failed pods after log recovery, which garbage-collected their approximately 9 TiB of retained generic-ephemeral PVCs. The PVCs persisted because Argo uses podGC.strategy: OnWorkflowSuccess, so failed Pod objects were never removed.
  • Submitted one Ada-pinned canary for histoscan/FR00313/rpe/v9/whole with 128 GiB memory: factory-histo-preprocessing-cccf4. Removed that in-flight case from the local queue, leaving 3,613 cases.
  • Opened mono PR #373, changing Factory’s call to preprocess_sample(..., visualize=False) and bumping the app version from 0.3.0 to 0.3.1. Local format, lint, typecheck, boundary, lock, and pre-commit checks passed.

Lessons Learned: Pitfalls

  • OpenAPI describes the sync request and response but does not expose the service’s per-workflow case limit. Treating one API call as one workflow could accidentally create many workflows.
  • The requested 256-case batch size conflicted with the service implementation’s 25-slide workflow size. Submission was paused until the user chose which constraint to preserve.
  • Mirroring the API’s 64 GiB RPE configuration was not safe for the filtered FR whole-slide cohort. The known level-0 full-array materialization in virdx-ome can require roughly six times a component’s gigapixels in GB, and the campaign was started without a representative canary on this cohort.

Lessons Learned: Improvements

  • Document service dispatch sizes in the Factory API or dashboard so operators can predict the number of workflows before submitting selected cases.
  • A dry-run response should ideally include actionable case count and projected workflow count rather than returning only an empty submitted-job list.
  • Factory should expose the supported virdx.dev/gpu-class choice so pinned submissions do not require reconstructing API-equivalent Workflow objects manually.
  • Before a large cohort submission, run a one-case canary from the largest expected whole slide and verify peak memory. For this retry, start with the first scan from the eight-minute failure at 128 GiB rather than resubmitting full 25-case batches blindly.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close