Commit 66fc53a1 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: DP rung D1 - K chained prepare-light+step cycles from ONE device entry

Self-chaining launcher pose_lma_dp_cycles: each cycle's launcher reads the
LIVE current_set (tail-enqueued behind the previous reduce_rms, so it sees
the accept flip a single unrolled parent could not), resolves cur/cand from
both physical sets in the PoseDpCycles descriptor, tail-enqueues the exact
host per-cycle sequence (clear-y = light memset, assemble(light),
fx_jacobian(current), prepare_finish(light), the six spike step stages),
then pose_lma_dp_commit - the device-side anchor update on accept that the
spike flagged as the multi-step gap - plus a minimal per-cycle 25-float
trace (ruling b), and finally the next launcher. Fixed-K, no device early
exit (ruling a). Interior cycles cross NOTHING host-side.

Wrapper tp_proc_exec_pose_lma_dp_cycles: entry H2D = pose vectors + anchor
+ descriptor; one launch; D2H once = trace + final result + final anchor +
set mirror. Preconditions: frozen conditioning (C3) + resident corr/peaks.

Gate: test_pose_lma_dp_cycles_jna (new, in cases.list as dp_cycles) seeds
resident measurement through the production chain (set_corr_indices_td ->
normalize -> corr2D_peak_eig_f), freezes conditioning, then compares three
chains (K=3 accept, K=2 reject, K=2 post-reject) against host-driven
light-prepare+step cycles: per-cycle traces, final packed, final anchor,
set index ALL BIT-IDENTICAL. run_cases.sh ALL PASS (pose_corr @tol 0,
peak, peak_recenter, avg_td, avg_td_oob, bayer_staged, dp_cycles); DP
spike test still PASS.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent ee0d275f
......@@ -65,6 +65,7 @@ case peak_recenter test_peak ${REPO}/testdata/synth4/peak_recenter
case avg_td test_avg_td ${REPO}/testdata/synth4/avg_td --tol 0
case avg_td_oob test_avg_td_oob ${REPO}/testdata/synth4/avg_td_oob --tol 0
case bayer_staged test_bayer_staged_jna ${REPO} --tol 0 # rung B4: synthetic, --data unused
case dp_cycles test_pose_lma_dp_cycles_jna ${REPO} --tol 0 # rung D1: synthetic, --data unused; DP K-cycle chain bit-exact vs host cycles # By Claude on 07/17/2026
#case convert_direct test_convert_direct ${REPO}/testdata/convert_direct_legacy # local blessed goldens (~200MB, make_convert_direct_case.py)
# gen <name(s)> <generator command, run from ${REPO}>
......
This diff is collapsed.
......@@ -701,6 +701,10 @@ struct TpProc {
int pose_prep_centers_tiles; // tile count the resident centers were uploaded for // By Claude on 07/17/2026
int pose_prep_frozen_tiles; // rung C3: tile count of the frozen conditioning (0 = no full prepare yet) // By Claude on 07/17/2026
PoseDpChain *gpu_pose_dp_chain; // DP spike: device copy of the chain descriptor // By Claude on 07/16/2026
// DP rung D1: device copy of the K-cycle chain descriptor + per-cycle result
// trace (num_cycles x POSE_LMA_RESIDENT_RESULT floats). By Claude on 07/17/2026.
PoseDpCycles *gpu_pose_dp_cycles;
float *gpu_pose_dp_trace; int pose_dp_trace_capacity;
// 3-B rung B1: per-sequence task template (packed txy + reference centers per
// selected task entry) for the resident task-build kernel. NULL template args
// on later calls mean "keep resident" (configuration-register semantics,
......@@ -793,6 +797,7 @@ TpProc* tp_proc_create(TpModule* m){
p->gpu_pose_lma_prep=nullptr; // By Claude on 07/17/2026
p->pose_prep_ready=0; p->pose_prep_centers_tiles=0; p->pose_prep_frozen_tiles=0; // By Claude on 07/17/2026 (rungs C2/C3)
p->gpu_pose_dp_chain=nullptr; // By Claude on 07/16/2026 (DP spike)
p->gpu_pose_dp_cycles=nullptr; p->gpu_pose_dp_trace=nullptr; p->pose_dp_trace_capacity=0; // By Claude on 07/17/2026 (rung D1)
p->gpu_pose_task_map=nullptr; p->gpu_pose_task_centers=nullptr; // By Claude on 07/16/2026 (rung B1)
p->pose_task_capacity=0; p->pose_task_count=0; // By Claude on 07/16/2026 (rung B1)
p->bayer_staging[0]=p->bayer_staging[1]=nullptr; // By Claude on 07/16/2026 (rung B4)
......@@ -2108,6 +2113,112 @@ int tp_proc_exec_pose_lma_dp_step(TpProc* p,
return 0;
}
// DP rung D1: K chained prepare-light + step cycles as ONE device-side entry
// (fixed measurement - the resident corr indices/peaks are re-assembled each
// cycle, exactly the host light-prepare + resident-step pair). Preconditions:
// a FULL resident prepare froze the conditioning at this tile count (rung C3)
// and the raw pose state is live. Per-call H2D = the 12-float pose vectors +
// the 3-float anchor + the chain descriptor; interior cycles cross NOTHING.
// D2H once at the end: per-cycle trace (num_cycles x 25), final result[25],
// final anchor, current_set mirror. By Claude on 07/17/2026.
int tp_proc_exec_pose_lma_dp_cycles(TpProc* p,
int num_cycles,
float lambda,
float pure_weight,
float rms_diff,
int num_tiles,
int corr_slot, int ntile_shift,
int num_components, int use_eigen,
float eig_min_sqrt, float eig_max_sqrt,
float min_confidence, int same_weights,
const float* pose_vectors,
const float* vector,
float* trace,
float* result,
float* vector_out){
if(!p||!result||!pose_vectors||!vector||(num_cycles<=0)||(num_tiles<=0)||
(num_components!=POSE_NUM_COMPONENTS)||
!std::isfinite(pure_weight)||!(pure_weight>0.0f)||!std::isfinite(rms_diff)){
seterr("exec_pose_lma_dp_cycles: bad args k=%d nt=%d nc=%d",num_cycles,num_tiles,num_components); return -1; }
if((p->pose_raw_num_tiles!=num_tiles)||!p->pose_raw_has_jacobian||
(p->pose_lma_value_capacity<POSE_NUM_COMPONENTS*num_tiles+POSE_NUM_PARAMS)||
!p->gpu_pose_lma_weights||!p->gpu_pose_lma_y||!p->gpu_pose_lma_eigen||
!p->gpu_pose_lma_vector||!p->gpu_pose_current_set||!p->gpu_pose_lma_prep){
seterr("exec_pose_lma_dp_cycles: resident prepared state not ready (nt=%d)",num_tiles); return -2; }
if(p->pose_prep_frozen_tiles!=num_tiles){ // light cycles need the frozen conditioning (rung C3)
seterr("exec_pose_lma_dp_cycles: conditioning not frozen (%d/%d)",
p->pose_prep_frozen_tiles,num_tiles); return -2; }
if(!p->gpu_corr_indices||!p->gpu_peaks||(p->last_num_corr_tiles<=0)){
seterr("exec_pose_lma_dp_cycles: no resident correlation/peaks (num_corr=%d)",
p->last_num_corr_tiles); return -2; }
cuCtxSetCurrent(p->mod->ctx);
const int num_partials=(num_tiles+POSE_LMA_REDUCE_THREADS-1)/POSE_LMA_REDUCE_THREADS;
if(!p->gpu_pose_dp_cycles &&
cudaMalloc((void**)&p->gpu_pose_dp_cycles,sizeof(PoseDpCycles))!=cudaSuccess){
seterr("exec_pose_lma_dp_cycles: chain alloc failed"); return -3; }
if(num_cycles>p->pose_dp_trace_capacity){
cudaFree(p->gpu_pose_dp_trace); p->gpu_pose_dp_trace=nullptr; p->pose_dp_trace_capacity=0;
if(cudaMalloc((void**)&p->gpu_pose_dp_trace,
(size_t)num_cycles*POSE_LMA_RESIDENT_RESULT*sizeof(float))!=cudaSuccess){
seterr("exec_pose_lma_dp_cycles: trace alloc failed k=%d",num_cycles); return -3; }
p->pose_dp_trace_capacity=num_cycles;
}
// Chain-entry state: pose vectors into the CURRENT set + the anchor - the
// same two uploads a host cycle would make; the chained cycles then keep
// both device-resident. By Claude on 07/17/2026.
if(cudaMemcpy(p->cur_pose_vectors(),pose_vectors,(size_t)4*POSE_NUM_PARAMS*sizeof(float),
cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_lma_vector,vector,POSE_NUM_PARAMS*sizeof(float),
cudaMemcpyHostToDevice)!=cudaSuccess){
seterr("exec_pose_lma_dp_cycles: entry HtoD failed"); return -4; }
PoseDpCycles h;
h.num_cycles=num_cycles; h.num_tiles=num_tiles;
h.num_corr=p->last_num_corr_tiles; h.num_partials=num_partials;
h.lambda=lambda; h.pure_weight=pure_weight; h.rms_diff=rms_diff;
h.corr_slot=corr_slot; h.ntile_shift=ntile_shift;
h.num_components=num_components; h.use_eigen=use_eigen;
h.eig_min_sqrt=eig_min_sqrt; h.eig_max_sqrt=eig_max_sqrt;
h.min_confidence=min_confidence; h.same_weights=same_weights;
h.corr_indices=p->gpu_corr_indices; h.peaks=p->gpu_peaks;
h.cameras=p->gpu_pose_cameras;
h.ref_radial=p->gpu_pose_radial[0]; h.ref_rbr=p->gpu_pose_rbr[0]; h.ref_ers=p->gpu_pose_ers[0];
h.scene_radial=p->gpu_pose_radial[1];h.scene_rbr=p->gpu_pose_rbr[1]; h.scene_ers=p->gpu_pose_ers[1];
h.centers=p->gpu_pose_centers; h.selection=p->gpu_pose_selection;
h.set_vectors[0]=p->gpu_pose_vectors; h.set_vectors[1]=p->gpu_pose_candidate_vectors;
h.set_fx[0]=p->gpu_pose_fx; h.set_fx[1]=p->gpu_pose_candidate_fx;
h.set_jt[0]=p->gpu_pose_jt; h.set_jt[1]=p->gpu_pose_candidate_jt;
h.set_valid[0]=p->gpu_pose_valid; h.set_valid[1]=p->gpu_pose_candidate_valid;
h.y=p->gpu_pose_lma_y; h.weights=p->gpu_pose_lma_weights; h.eigen=p->gpu_pose_lma_eigen;
h.vector=p->gpu_pose_lma_vector;
h.regweights=p->gpu_pose_lma_prep+POSE_LMA_PREP_RESULT+POSE_NUM_PARAMS;
h.jt_out=p->gpu_pose_lma_jt; h.ymfx_out=p->gpu_pose_lma_ymfx; h.partials=p->gpu_pose_lma_partials;
h.cand_jt_out=p->gpu_pose_lma_candidate_jt; h.cand_ymfx_out=p->gpu_pose_lma_candidate_ymfx;
h.cand_partials=p->gpu_pose_lma_candidate_partials;
h.result=p->gpu_pose_lma_result; h.prep_result=p->gpu_pose_lma_prep;
h.current_set=p->gpu_pose_current_set;
h.trace=trace?p->gpu_pose_dp_trace:nullptr;
if(cudaMemcpy(p->gpu_pose_dp_cycles,&h,sizeof(PoseDpCycles),cudaMemcpyHostToDevice)!=cudaSuccess){
seterr("exec_pose_lma_dp_cycles: chain HtoD failed"); return -4; }
CUfunction dp=getfun(p->mod,"pose_lma_dp_cycles");
if(!dp){ seterr("pose_lma_dp_cycles missing (DP kernel not in module)"); return -5; }
int cycle0=0;
void* da[]={ &p->gpu_pose_dp_cycles, &cycle0 };
if(launch1(dp,1,1,1,1,1,1,da,"pose_lma_dp_cycles")) return -6;
if((trace &&
cudaMemcpy(trace,p->gpu_pose_dp_trace,
(size_t)num_cycles*POSE_LMA_RESIDENT_RESULT*sizeof(float),cudaMemcpyDeviceToHost)!=cudaSuccess) ||
cudaMemcpy(result,p->gpu_pose_lma_result,
POSE_LMA_RESIDENT_RESULT*sizeof(float),cudaMemcpyDeviceToHost)!=cudaSuccess ||
(vector_out &&
cudaMemcpy(vector_out,p->gpu_pose_lma_vector,
POSE_NUM_PARAMS*sizeof(float),cudaMemcpyDeviceToHost)!=cudaSuccess)){
seterr("exec_pose_lma_dp_cycles: DtoH failed"); return -7; }
if(cudaMemcpy(&p->pose_current_set,p->gpu_pose_current_set,sizeof(int),
cudaMemcpyDeviceToHost)!=cudaSuccess){
seterr("exec_pose_lma_dp_cycles: current_set DtoH failed"); return -7; }
return 0;
}
int tp_proc_exec_pose_lma_resident_step(TpProc* p,
float lambda,
const float* weights,
......@@ -2529,6 +2640,7 @@ void tp_proc_destroy(TpProc* p){
cudaFree(p->gpu_pose_lma_candidate_partials); // By Codex on 07/15/2026
cudaFree(p->gpu_pose_lma_prep); // By Claude on 07/17/2026
cudaFree(p->gpu_pose_dp_chain); // By Claude on 07/16/2026 (DP spike)
cudaFree(p->gpu_pose_dp_cycles); cudaFree(p->gpu_pose_dp_trace); // By Claude on 07/17/2026 (rung D1)
cudaFree(p->gpu_pose_task_map); cudaFree(p->gpu_pose_task_centers); // By Claude on 07/16/2026 (rung B1)
delete p;
}
......
......@@ -1121,6 +1121,122 @@ extern "C" __global__ void pose_lma_dp_step(const PoseDpChain * c)
c->cand_partials, c->result, c->current_set);
}
// ── DP rung D1: K chained prepare-light + step cycles, fixed measurement ─────
// See tp_lma.h (PoseDpCycles). The launcher replicates the host per-cycle
// sequence EXACTLY: [clear measured y region = the light cudaMemset] ->
// pose_lma_assemble(light) -> pose_fx_jacobian(current) ->
// pose_lma_prepare_finish(light) -> the six spike step stages ->
// pose_lma_dp_commit (anchor update on accept - the spike's noted gap - plus
// the per-cycle trace snapshot) -> the NEXT launcher. The pose vectors need no
// per-cycle upload: after an accept flip the current set already holds the
// candidate built by pose_lma_dp_candidate_vector (bit-identical to the host's
// re-upload); after a reject the current set is untouched. By Claude on 07/17/2026.
// Device equivalent of the light-prepare cudaMemset on the measured y region.
extern "C" __global__ void pose_lma_dp_clear(
int n,
float * data)
{
const int i = (int) (blockIdx.x * blockDim.x + threadIdx.x);
if (i < n) data[i] = 0.0f;
}
// Post-step commit: the linearization anchor must track the ACCEPTED pose
// (a stale anchor makes every following candidate overshoot - the spike's
// test-authoring lesson). Reject keeps the anchor, exactly like the host
// re-uploading an unchanged parameters_vector. Optional 25-float snapshot
// into the per-cycle trace (ruling b: minimal convenient trace).
extern "C" __global__ void pose_lma_dp_commit(
const float * result,
float * vector,
float * trace)
{
if ((blockIdx.x != 0) || (threadIdx.x != 0)) return;
if (result[POSE_LMA_ACCEPTED] != 0.0f) {
#pragma unroll
for (int par = 0; par < POSE_NUM_PARAMS; par++) {
vector[par] = result[16 + par];
}
}
if (trace != NULL) {
for (int i = 0; i < POSE_LMA_RESIDENT_RESULT; i++) {
trace[i] = result[i];
}
}
}
extern "C" __global__ void pose_lma_dp_cycles(const PoseDpCycles * c, int cycle)
{
if ((blockIdx.x != 0) || (threadIdx.x != 0)) return;
// LIVE set read: this launcher runs AFTER the previous cycle's
// pose_lma_reduce_rms (it was tail-enqueued behind it), so it sees the flip.
const int set = *c->current_set;
float * cur_vectors = c->set_vectors[set];
float * cur_fx = c->set_fx[set];
float * cur_jt = c->set_jt[set];
unsigned char * cur_valid = c->set_valid[set];
float * cand_vectors= c->set_vectors[set ^ 1];
float * cand_fx = c->set_fx[set ^ 1];
float * cand_jt = c->set_jt[set ^ 1];
unsigned char * cand_valid = c->set_valid[set ^ 1];
const float * candidate_vector = cand_vectors + 3 * POSE_NUM_PARAMS;
const int pose_threads = 256;
const int pose_blocks = (c->num_tiles + pose_threads - 1) / pose_threads;
const int a_threads = 256; // host assemble launch shape
const int a_blocks = (c->num_corr + a_threads - 1) / a_threads;
const int clear_n = c->num_components * c->num_tiles;
const int clear_blocks = (clear_n + 255) / 256;
const int light = 1;
// prepare-light (exec_pose_lma_prepare_resident light=1, same grids):
pose_lma_dp_clear<<<clear_blocks, 256, 0, cudaStreamTailLaunch>>>(
clear_n, c->y);
pose_lma_assemble<<<a_blocks, a_threads, 0, cudaStreamTailLaunch>>>(
c->num_corr, c->num_tiles, c->corr_slot, c->ntile_shift,
c->num_components, c->use_eigen, c->eig_min_sqrt, c->eig_max_sqrt,
c->min_confidence, c->same_weights, light,
c->corr_indices, c->peaks,
c->y, c->weights, c->eigen, c->selection);
pose_fx_jacobian<<<pose_blocks, pose_threads, 0, cudaStreamTailLaunch>>>(
c->num_tiles, 1, c->cameras,
c->ref_radial, c->ref_rbr, c->ref_ers,
c->scene_radial, c->scene_rbr, c->scene_ers,
cur_vectors, c->centers, c->selection,
cur_fx, cur_jt, cur_valid);
pose_lma_prepare_finish<<<1, 1, 0, cudaStreamTailLaunch>>>(
c->num_tiles, c->num_components, POSE_NUM_PARAMS, light,
cur_fx, cur_jt, cur_valid, c->eigen, c->regweights,
c->y, c->weights, c->prep_result);
// the resident step (the six validated spike stages):
pose_lma_prepare_products<<<c->num_partials, POSE_LMA_REDUCE_THREADS, 0, cudaStreamTailLaunch>>>(
c->num_tiles, cur_fx, cur_jt, cur_valid,
c->y, c->weights, c->eigen, c->vector,
c->jt_out, c->ymfx_out, c->partials);
pose_lma_reduce_step<<<1, 1, 0, cudaStreamTailLaunch>>>(
c->num_partials, c->lambda, c->pure_weight,
c->partials, c->vector, c->result);
pose_lma_dp_candidate_vector<<<1, 1, 0, cudaStreamTailLaunch>>>(
cur_vectors, c->result, cand_vectors);
pose_fx_jacobian<<<pose_blocks, pose_threads, 0, cudaStreamTailLaunch>>>(
c->num_tiles, 1, c->cameras,
c->ref_radial, c->ref_rbr, c->ref_ers,
c->scene_radial, c->scene_rbr, c->scene_ers,
cand_vectors, c->centers, c->selection,
cand_fx, cand_jt, cand_valid);
pose_lma_prepare_products<<<c->num_partials, POSE_LMA_REDUCE_THREADS, 0, cudaStreamTailLaunch>>>(
c->num_tiles, cand_fx, cand_jt, cand_valid,
c->y, c->weights, c->eigen, candidate_vector,
c->cand_jt_out, c->cand_ymfx_out, c->cand_partials);
pose_lma_reduce_rms<<<1, 1, 0, cudaStreamTailLaunch>>>(
c->num_partials, c->pure_weight, c->rms_diff,
c->cand_partials, c->result, c->current_set);
pose_lma_dp_commit<<<1, 1, 0, cudaStreamTailLaunch>>>(
c->result, c->vector,
(c->trace != NULL) ? (c->trace + (size_t) cycle * POSE_LMA_RESIDENT_RESULT) : NULL);
if (cycle + 1 < c->num_cycles) {
pose_lma_dp_cycles<<<1, 1, 0, cudaStreamTailLaunch>>>(c, cycle + 1);
}
}
#endif // __CUDACC_RTC__
extern "C" __global__ void lma_normal_products(
......
......@@ -257,6 +257,60 @@ typedef struct {
int * current_set; // device-authoritative accept flip (rung B)
} PoseDpChain;
// ── DP rung D1 (ladder ratified 2026-07-16): K chained prepare-light + step
// cycles from ONE device-side entry, fixed measurement. The launcher kernel
// pose_lma_dp_cycles resolves CURRENT/candidate from the LIVE *current_set at
// each cycle start (a single unrolled parent cannot: tail-launch children run
// after the parent exits, so cycle k+1's pointer choice depends on a flip the
// parent has not seen - hence the SELF-CHAINING launcher, one per cycle, each
// tail-enqueueing its stages and then the next launcher). Both physical buffer
// sets cross in the struct; per-cycle H2D is ZERO. Fixed-K, no device early
// exit (ruling a); per-cycle result snapshots to a minimal trace (ruling b).
// By Claude on 07/17/2026.
typedef struct {
int num_cycles;
int num_tiles;
int num_corr; // resident correlation rows (assemble grid)
int num_partials;
float lambda; // production policy: fixed lambda0, inner=1
float pure_weight;
float rms_diff;
// pose_lma_assemble(light) policy args (frozen conditioning, rung C3)
int corr_slot;
int ntile_shift;
int num_components;
int use_eigen;
float eig_min_sqrt;
float eig_max_sqrt;
float min_confidence;
int same_weights;
const int * corr_indices;
const float * peaks;
// camera/geometry (shared, per-sequence/per-scene resident)
const PoseCameraMeta * cameras;
const float * ref_radial; const float * ref_rbr; const float * ref_ers;
const float * scene_radial;const float * scene_rbr;const float * scene_ers;
const float * centers;
unsigned char * selection; // read-only in light cycles (frozen)
// FIXED physical slots (rung B): [0] = gpu_pose_*, [1] = gpu_pose_candidate_*;
// the launcher picks cur = set[*current_set], cand = the other, LIVE per cycle.
float * set_vectors[2];
float * set_fx[2];
float * set_jt[2];
unsigned char * set_valid[2];
// shared LMA buffers (resident prepared state)
float * y; float * weights; float * eigen;
float * vector; // linearization anchor (gpu_pose_lma_vector);
// updated DEVICE-SIDE on accept (the spike's noted gap)
const float * regweights; // frozen; unused by light finish but part of its contract
float * jt_out; float * ymfx_out; float * partials;
float * cand_jt_out; float * cand_ymfx_out; float * cand_partials;
float * result; // [POSE_LMA_RESIDENT_RESULT]
float * prep_result; // light finish contract arg (untouched in light mode)
int * current_set;
float * trace; // [num_cycles][POSE_LMA_RESIDENT_RESULT], NULL = off
} PoseDpCycles;
extern "C" __global__ void lma_normal_products(
int num_params,
int num_values,
......
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