Commit f32c1f18 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-B rung B1 - lean path switches to the resident GPU task build

leanMeasure (uniform-MB lean path, JNA backend): the per-cycle CPU front
end - transformToScenePxPyD (full 5120-tile grid), setInterTasksMotionBlur
+ sort, and the two task H2D uploads - is replaced by ONE
gpuQuad.execPoseTaskUpdate() call: pose_task_update projects the
per-sequence template on-device from the Java-tracked measure pose and
rewrites both resident task slots in place; geometry + convert then run
on ACTIVATED slots with no upload (ImageDtt.interCorrTDResident). Per
cycle the only H2D left is the 12-float pose vector. Per scene:
setupERS + camera-block upload (rung C2 registers, shared with
prepare_resident) + skeleton slot re-upload (~120 KB, guards against
other pipeline stages using the task slots between scenes). The
per-scene uniform-MB 6-float descriptor replicates the exact
setInterTasksMotionBlur double crank math; margin/projection failures
become task=0 holes (missing peak -> conditioning abstains, D3 design).
Fallbacks: JCuda backend, pose_mb_uniform off, NaN/non-uniform MB
(degenerate uniformMotionBlur fallback) -> unchanged legacy CPU build.

- GpuQuad/GpuQuadJna/TpJna: execPoseTaskUpdate + activateTaskSlot
  (base returns false; JNA marshals rung-C2 nullable groups).
- IntersceneLmaFloat.buildTasks: serial float task-build oracle (the
  Java float clone of the kernel, worldFromPixel -> pixelFromWorld +
  descriptor/margin/hole packing).
- One-shot oracle at pose_lma_debug>=1: GPU stream vs the legacy Java
  DOUBLE build (txy-matched field compare, D3 gate <=1e-5 px print) and
  vs the float-serial clone; once-per-program path-active note.
- pose_corr export stays the explicit diagnostic exception: armed runs
  read the GPU-built pre-offset streams back and capture task words RAW
  (new iterTasksPre or511 overload - a |511-ed hole would diverge in a
  tol-0 replay).

mvn -DskipTests clean package: BUILD SUCCESS. Real-scene gate (D3,
ratified): poses < 0.003 px drift, aggregate RMS unchanged at print
precision, same QC count, 497/497 - Andrey's run.

CUDA side: tile_processor_gpu lwir16_2 @ ef36688 (kernel + JNA API +
two-tier tests, all regressions PASS incl. pose_corr @tol 0).
Design: internal handoffs/2026-07-16_3b_measure_chain_residency_design.md.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 6c10fe38
......@@ -2984,6 +2984,51 @@ public class GpuQuad{ // quad camera description
return null;
}
/**
* 3-B rung B1 (design 2026-07-16): resident task build - pose_task_update runs
* the C1-validated projection chain over the per-sequence task template and
* rewrites the 6-float task headers of BOTH resident slots in place (slot 0 =
* MB main, slot 1 = MB partner; margin/projection failures become task=0
* holes). Nullable groups follow the rung-C2 configuration-register contract.
* Task slots must hold the per-sequence skeleton (setTasks to slots 0 and 1)
* with exactly num_tasks entries. Base/JCuda returns false so the Java task
* build remains the fallback. By Claude on 07/16/2026.
*/
public boolean execPoseTaskUpdate(
IntersceneLmaFloat.Camera reference, // null = keep resident
IntersceneLmaFloat.Camera scene, // null = keep resident
float [] reference_xyz, // pose group: all four null = device current set
float [] reference_atr,
float [] scene_xyz,
float [] scene_atr,
int [] task_map, // null = keep resident template
float [] task_centers, // null = keep resident template
int num_tasks,
float disparity_corr,
float min_px,
float max_px,
float min_py,
float max_py,
int task_code_main, // pre-encoded (|511) task words
int task_code_sub,
float dx_main, // uniform-MB descriptor (offsets from the projected point)
float dy_main,
float dx_sub,
float dy_sub,
float scale_main, // pre-encoded scale fields (TpTask.setScale semantics)
float scale_sub) {
return false;
}
/**
* 3-B rung B1: make a resident task slot the live kernel stream WITHOUT an
* H2D upload (after execPoseTaskUpdate rewrote it in place). Base/JCuda
* returns false (no resident slots). By Claude on 07/16/2026.
*/
public boolean activateTaskSlot(int slot) {
return false;
}
public void execRBGA(
double [] color_weights,
boolean is_lwir,
......
......@@ -634,6 +634,83 @@ public class GpuQuadJna extends GpuQuad {
outY, outWeights, outEigen);
}
// ---- 3-B rung B1: resident task build (design 2026-07-16) ---- // By Claude on 07/16/2026
@Override public boolean execPoseTaskUpdate(
final IntersceneLmaFloat.Camera reference, // null = keep resident
final IntersceneLmaFloat.Camera scene, // null = keep resident
final float[] reference_xyz, // all four null = keep device current set
final float[] reference_atr,
final float[] scene_xyz,
final float[] scene_atr,
final int[] task_map, // null = keep resident template
final float[] task_centers, // null = keep resident template
final int num_tasks,
final float disparity_corr,
final float min_px,
final float max_px,
final float min_py,
final float max_py,
final int task_code_main,
final int task_code_sub,
final float dx_main,
final float dy_main,
final float dx_sub,
final float dy_sub,
final float scale_main,
final float scale_sub) {
if ((num_tasks <= 0) ||
((task_map != null) != (task_centers != null)) ||
((task_map != null) && ((task_map.length != num_tasks) ||
(task_centers.length != 3 * num_tasks)))) {
throw new IllegalArgumentException("GpuQuadJna.execPoseTaskUpdate: inconsistent inputs");
}
final boolean have_pose = (scene_xyz != null) || (scene_atr != null) ||
(reference_xyz != null) || (reference_atr != null);
float[] poseVectors = null;
if (have_pose) {
poseVectors = new float[12];
copyPoseVector3(reference_xyz, poseVectors, 0);
copyPoseVector3(reference_atr, poseVectors, 3);
copyPoseVector3(scene_xyz, poseVectors, 6);
copyPoseVector3(scene_atr, poseVectors, 9);
}
final int rc = lib.tp_proc_exec_pose_task_update(
proc,
(reference != null) ? poseCameraMeta(reference) : null,
(reference != null) ? reference.radial : null,
(reference != null) ? reference.rByRDist : null,
(reference != null) ? reference.rByRDist.length : 0,
(reference != null) ? reference.ers : null,
(reference != null) ? reference.ers.length : 0,
(scene != null) ? poseCameraMeta(scene) : null,
(scene != null) ? scene.radial : null,
(scene != null) ? scene.rByRDist : null,
(scene != null) ? scene.rByRDist.length : 0,
(scene != null) ? scene.ers : null,
(scene != null) ? scene.ers.length : 0,
poseVectors, task_map, task_centers,
num_tasks, getNumSensors(),
disparity_corr,
min_px, max_px, min_py, max_py,
task_code_main, task_code_sub,
dx_main, dy_main, dx_sub, dy_sub,
scale_main, scale_sub);
if (rc != 0) {
throw new IllegalStateException("GpuQuadJna.execPoseTaskUpdate rc="+rc+": "+
lib.tp_last_error());
}
return true;
}
@Override public boolean activateTaskSlot(final int slot) { // By Claude on 07/16/2026
final int rc = lib.tp_proc_activate_tasks_slot(proc, slot);
if (rc != 0) {
throw new IllegalStateException("GpuQuadJna.activateTaskSlot("+slot+") rc="+rc+": "+
lib.tp_last_error());
}
return true;
}
// ---- resident float pose preparation/reduction continuation (roadmap rung 3-A4e) ----
@Override public IntersceneLmaFloat.ResidentLmaStepResult execPoseLmaResidentStep(
final float lambda,
......
......@@ -89,6 +89,9 @@ public interface TpJna extends Library {
/** Explicit persistent task slots (0/1); the selected slot becomes the live kernel stream. */
int tp_proc_set_tasks_slot(Pointer proc, float[] ftasks, int ntiles, int totalFloats, int slot);
int tp_proc_get_tasks_slot(Pointer proc, float[] out, int totalFloats, int slot);
/** 3-B rung B1: make a resident slot the live kernel task stream with NO upload
* (after pose_task_update rewrote it in place). By Claude on 07/16/2026. */
int tp_proc_activate_tasks_slot(Pointer proc, int slot);
int tp_proc_exec_geometry(Pointer proc, int uniformGrid);
/** convert_direct: ref_scene (0/1 -> clt vs clt_ref), erase_clt (-1/0/1), no_kernels (0/1). */
int tp_proc_exec_convert_direct(Pointer proc, int refScene, int eraseClt, int noKernels);
......@@ -182,6 +185,26 @@ public interface TpJna extends Library {
int light, // rung C3: 1 = frozen conditioning, fresh y only // By Claude on 07/17/2026
float[] prepResult, int capture,
float[] outY, float[] outWeights, float[] outEigen);
/** 3-B rung B1 (design 2026-07-16): resident task build. pose_task_update runs the
* C1-validated projection chain over the per-sequence task template and rewrites
* the 6-float task headers of BOTH resident slots in place. Nullable groups follow
* the rung-C2 configuration-register contract: camera blocks (shared with
* prepare_resident), taskMap/taskCenters (per-sequence template), poseVectors
* (null = device-authoritative current set). Task slots must hold the
* per-sequence skeleton (set_tasks_slot) with exactly numTasks entries.
* By Claude on 07/16/2026. */
int tp_proc_exec_pose_task_update(Pointer proc,
float[] refMeta, float[] refRadial,
float[] refRbr, int refRbrLen, float[] refErs, int refErsLen,
float[] sceneMeta, float[] sceneRadial,
float[] sceneRbr, int sceneRbrLen, float[] sceneErs, int sceneErsLen,
float[] poseVectors, int[] taskMap, float[] taskCenters,
int numTasks, int numCams,
float disparityCorr,
float minPx, float maxPx, float minPy, float maxPy,
int taskCodeMain, int taskCodeSub,
float dxMain, float dyMain, float dxSub, float dySub,
float scaleMain, float scaleSub);
/** Deterministic double normal-equation products for the lean pose LMA.
* jt is parameter-major [numParams][numValues]; out is row-major H followed by b.
* Damping and the small solve remain in Java. 0 on success. */
......
......@@ -184,6 +184,24 @@ public class PoseCorrExport {
GpuQuad gpuQuad,
QuadCLT scene,
TpTask [][] tp_tasks) {
iterTasksPre(clt_parameters, image_dtt, gpuQuad, scene, tp_tasks, true);
}
/**
* 3-B rung B1 overload: or511=false captures the task words RAW. On the
* GPU-task-build path the pre streams are read back from the resident slots
* (updateTasks D2H) - the |511 OR already happened when the per-sequence
* skeleton was uploaded, and task=0 HOLES must stay 0 so a tol-0 replay
* uploads exactly what the production GPU held (a |511-ed hole would get
* converted by index_direct and diverge). By Claude on 07/16/2026.
*/
public static synchronized void iterTasksPre(
CLTParameters clt_parameters,
ImageDtt image_dtt,
GpuQuad gpuQuad,
QuadCLT scene,
TpTask [][] tp_tasks,
boolean or511) {
if (!armed || done) return;
if (bound_scene == null) { // bind to this scene
if ((images_scene == null) || !images_scene.equals(scene.getImageName())) {
......@@ -200,8 +218,8 @@ public class PoseCorrExport {
} else if (!bound_scene.equals(scene.getImageName())) {
return; // another scene (bound scene failed mid-fit and was reported) - keep what we have
}
tasks0_pre.add(flattenTasks(tp_tasks[0], true));
tasks1_pre.add((tp_tasks.length > 1) && (tp_tasks[1] != null) ? flattenTasks(tp_tasks[1], true) : null);
tasks0_pre.add(flattenTasks(tp_tasks[0], or511));
tasks1_pre.add((tp_tasks.length > 1) && (tp_tasks[1] != null) ? flattenTasks(tp_tasks[1], or511) : null);
}
/**
......
......@@ -1449,9 +1449,66 @@ public class ImageDtt extends ImageDttCPU {
}
return;
}
/**
* 3-B rung B1 (design 2026-07-16): the interCorrTDMotionBlur convert flow for
* tasks that are ALREADY resident - pose_task_update rewrote both native task
* slots on-device, so there is no per-cycle task H2D: each slot is only
* ACTIVATED (no upload), then geometry + convert run exactly as in
* interCorrTDMotionBlur (slot 0 = MB main, erase per erase_clt_first, SET;
* slot 1 = MB partner, no erase, SUBTRACT). LPF constant setup is kept
* identical to the legacy flow. Convert-only (the leanMeasure caller
* correlates through the consolidation bridge). Requires a backend with
* resident task slots (GpuQuad.activateTaskSlot). By Claude on 07/16/2026.
*/
public void interCorrTDResident(
final int erase_clt_first,
final double gpu_sigma_r,
final double gpu_sigma_b,
final double gpu_sigma_g,
final double gpu_sigma_m,
final double gpu_sigma_rb_corr,
final double gpu_sigma_corr,
final double gpu_sigma_log_corr,
final int globalDebugLevel)
{
final float [][] lpf_rgb = new float[][] {
floatGetCltLpfFd(gpu_sigma_r),
floatGetCltLpfFd(gpu_sigma_b),
floatGetCltLpfFd(gpu_sigma_g),
floatGetCltLpfFd(gpu_sigma_m)
};
gpuQuad.setLpfRbg( // constants memory - same for all cameras
lpf_rgb,
globalDebugLevel > 2);
final float [] lpf_flat = floatGetCltLpfFd(gpu_sigma_corr);
gpuQuad.setLpfCorr(
"lpf_corr",
lpf_flat,
globalDebugLevel > 2);
final float [] lpf_rb_flat = floatGetCltLpfFd(gpu_sigma_rb_corr);
gpuQuad.setLpfCorr(
"lpf_rb_corr",
lpf_rb_flat,
globalDebugLevel > 2);
final float [] log_flat = floatGetCltHpfFd(gpu_sigma_log_corr);
gpuQuad.setLpfCorr(
"LoG_corr",
log_flat,
globalDebugLevel > 2);
// primary set (MB main): activate resident slot 0, offsets, convert-SET
if (!gpuQuad.activateTaskSlot(0)) {
throw new IllegalStateException("interCorrTDResident: backend has no resident task slots");
}
gpuQuad.execSetTilesOffsets(false); // NON-UNIFORM grid (centerXY from pose_task_update)
gpuQuad.execConvertDirect(false, null, erase_clt_first);
// secondary set (MB partner): activate resident slot 1, offsets, convert-SUBTRACT
gpuQuad.activateTaskSlot(1);
gpuQuad.execSetTilesOffsets(false);
gpuQuad.execConvertDirect(false, null, -1);
}
/**
* Convert reference scene to FD and save result in extra GPU array for the future interscene correlation
* Geometry correction and images will come from gpuQuad instance -
......
......@@ -793,6 +793,96 @@ public final class IntersceneLmaFloat {
return new Result(numTiles, fx, jt, valid, numValid);
}
/**
* 3-B rung B1 (design 2026-07-16): serial float task-build oracle - the Java
* float clone of the pose_task_update CUDA kernel. Runs the SAME float
* projection chain as {@link #getFxDerivs} (worldFromPixel -> pixelFromWorld)
* over the per-sequence task template and packs the 6-float task headers of
* both MB sets exactly as the kernel does: centerXY = projected point + the
* uniform-MB descriptor offsets, target_disparity = scene disparity +
* disparity_corr, pre-encoded task/scale words, task=0 HOLES (all header
* floats zero except txy) for failed projections and margin-gate rejects.
* Differences vs the CUDA stream are float-scheduling only (FMA contraction);
* the one-shot leanMeasure compare prints the max field deltas.
* @return float[2][num_tasks*task_size] header-filled task streams
* (set 0 = MB main, set 1 = MB partner); xy/disp_dist tails zero.
* By Claude on 07/16/2026.
*/
public static float [][] buildTasks(
final Camera reference,
final Camera scene,
final float [] referenceXyz,
final float [] referenceAtr,
final float [] sceneXyz,
final float [] sceneAtr,
final int [] taskMap, // packed txy: tx | (ty<<16)
final float [] taskCenters, // reference px,py,disparity per task
final int taskSize,
final float disparityCorr,
final float minPx,
final float maxPx,
final float minPy,
final float maxPy,
final int taskCodeMain,
final int taskCodeSub,
final float dxMain,
final float dyMain,
final float dxSub,
final float dySub,
final float scaleMain,
final float scaleSub) {
if ((taskMap == null) || (taskCenters == null) ||
(taskCenters.length != 3 * taskMap.length) || (taskSize < 6)) {
throw new IllegalArgumentException("buildTasks: inconsistent template");
}
checkVector3(referenceXyz, "referenceXyz");
checkVector3(referenceAtr, "referenceAtr");
checkVector3(sceneXyz, "sceneXyz");
checkVector3(sceneAtr, "sceneAtr");
final int numTasks = taskMap.length;
final float [] refInverse = rotationMatrices(referenceAtr, true);
final float [] sceneDirect = rotationMatrices(sceneAtr, false);
final float [][] streams = new float [2][numTasks * taskSize];
for (int task = 0; task < numTasks; task++) {
final int base = task * taskSize;
streams[0][base + 1] = Float.intBitsToFloat(taskMap[task]); // txy in both sets
streams[1][base + 1] = streams[0][base + 1];
final int ci = 3 * task;
final float px = taskCenters[ci];
final float py = taskCenters[ci + 1];
final float disparity = taskCenters[ci + 2];
if (!finite(px) || !finite(py) || !finite(disparity)) continue; // hole
final boolean infinity = disparity < INFINITY_DISPARITY;
final WorldDerivs referenceWorld = worldFromPixel(
reference, px, py, disparity, infinity, referenceXyz, refInverse);
if (referenceWorld == null) continue; // hole
final float [] scenePixel = pixelFromWorld(
scene, referenceWorld.world, infinity, sceneXyz, sceneDirect);
if (scenePixel == null) continue; // hole
final float cxMain = scenePixel[0] + dxMain;
final float cyMain = scenePixel[1] + dyMain;
final float cxSub = scenePixel[0] + dxSub;
final float cySub = scenePixel[1] + dySub;
if ((cxMain < minPx) || (cxMain > maxPx) || (cyMain < minPy) || (cyMain > maxPy) ||
(cxSub < minPx) || (cxSub > maxPx) || (cySub < minPy) || (cySub > maxPy)) {
continue; // margin-gate hole
}
final float targetDisparity = scenePixel[2] + disparityCorr;
if (!finite(targetDisparity)) continue; // hole
streams[0][base] = Float.intBitsToFloat(taskCodeMain);
streams[1][base] = Float.intBitsToFloat(taskCodeSub);
streams[0][base + 2] = targetDisparity;
streams[1][base + 2] = targetDisparity;
streams[0][base + 3] = cxMain;
streams[0][base + 4] = cyMain;
streams[1][base + 3] = cxSub;
streams[1][base + 4] = cySub;
streams[0][base + 5] = scaleMain;
streams[1][base + 5] = scaleSub;
}
return streams;
}
/** Compare a float result with the exact legacy double geometry call. */
public static Comparison compareToDouble(
final Result result,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment