Commit 5bee1911 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: Batch the DGX remote DNN path + on-GPU ghostbuster

runDnnRemote requests each level's scenes in chunks (REQ=64) via CuasDnnRemote.inferBatch instead of per-scene; the DGX runs them continuously (production-representative ~100ms/scene full-res) and applies the ghostbuster on the GPU in decode, so BOTH the ROI 121-cell field and the full-frame -OFFSET {dx,dy,s,Vx,Vy} are ghostbusted (dropped the Java-side ghostbust). Validated: local vs remote on the same weighted9_pm_s model -> max |diff| ~1e-4 (ORT per-pixel vs PyTorch shift-and-stitch fp). Full-res ~100ms/scene is the oracle; RT would use the 1/4-res single forward (~4.4ms).
Co-Authored-By: 's avatarClaude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 01a6e535
......@@ -728,21 +728,29 @@ public class CuasDetectRT {
double [][][] off5 = new double [5][num][H*W]; // {dx,dy,s,Vx,Vy} full-frame -> -OFFSET
String [] ts_dnn = new String [num];
System.out.println(now()+" runDnnRemote(): LEV"+nlev+" "+num+" of "+num_all+" scenes, stride "+dnn_stride+", ROI "+roi.width+"x"+roi.height);
for (int j = 0; j < num; j++) {
int newest = (w0 + j)*dnn_stride + N_dnn - 1; // newest frame index in this level (same as local)
// batched: request the level's in-window scenes in chunks; the DGX runs them continuously
// (production throughput) and ghostbusts on the GPU (so -OFFSET s + ROI 121 are both clean -
// no Java-side ghostbust). rmax_cells = vmax*vel_decimate (<=0 disables). By Claude on 06/20/2026
double rmax = (clt_parameters.imp.curt_dnn_vmax > 0) ?
clt_parameters.imp.curt_dnn_vmax * clt_parameters.imp.curt_vel_decimate : 0.0;
final int REQ = 64; // scenes per round-trip (reply byte[] ~419MB at 640x512, < 2GB array cap)
for (int j0 = 0; j0 < num; j0 += REQ) {
int cnt = Math.min(REQ, num - j0);
int startNewest = (w0 + j0)*dnn_stride + N_dnn - 1; // absolute newest of the chunk's first scene
long t0 = System.currentTimeMillis();
CuasDnnRemote.Result r = remote.infer(nlev, newest, roi);
CuasDnnRemote.BatchResult br = remote.inferBatch(nlev, startNewest, cnt, dnn_stride, roi, rmax);
long t1 = System.currentTimeMillis();
double [][][] fld = new double [rnp][1][r.nvel];
for (int p = 0; p < rnp; p++) { float [] rv = r.roiField[p]; double [] dv = fld[p][0]; for (int v = 0; v < r.nvel; v++) dv[v] = rv[v]; }
// ghostbuster: zero untrained corner-velocity cells + ghost-peak pixels, matching the local CPU path
// (the DGX returns the raw softmax*s field) - removes the corner sidelobe noise. By Claude on 06/20/2026
if (clt_parameters.imp.curt_dnn_vmax > 0)
dnnGhostbust(fld, null, clt_parameters.imp.curt_vel_radius, clt_parameters.imp.curt_dnn_vmax * clt_parameters.imp.curt_vel_decimate);
for (int jj = 0; jj < cnt; jj++) {
int j = j0 + jj;
double [][][] fld = new double [rnp][1][br.nvel];
for (int p = 0; p < rnp; p++) { float [] rv = br.roiField[jj][p]; double [] dv = fld[p][0]; for (int v = 0; v < br.nvel; v++) dv[v] = rv[v]; }
dnn_roi[j] = fld;
for (int c = 0; c < 5; c++) { float [] sc = r.offset5[c]; double [] dc = off5[c][j]; for (int p = 0; p < H*W; p++) dc[p] = sc[p]; }
for (int c = 0; c < 5; c++) { float [] sc = br.offset5[jj][c]; double [] dc = off5[c][j]; for (int p = 0; p < H*W; p++) dc[p] = sc[p]; }
int newest = (w0 + j)*dnn_stride + N_dnn - 1;
ts_dnn[j] = ts_pyramid[nlev][newest] + " f"+newest;
System.out.println(now()+" DNN-remote scene "+(j+1)+"/"+num+" (f"+newest+"): gpu_exec="+d2s(r.gpuMs)+"ms roundtrip="+(t1-t0)+"ms");
}
System.out.println(now()+" DNN-remote LEV"+nlev+" scenes "+(j0+1)+".."+(j0+cnt)+"/"+num
+": gpu="+d2s(br.gpuMs)+"ms ("+d2s(br.gpuMs/cnt)+"ms/scene) roundtrip="+(t1-t0)+"ms");
}
String roiTag = "-ROI"+roi.x+"_"+roi.y+"_"+roi.width+"_"+roi.height;
String title = title_conv5d+"-DNN"+((nlev>0)?("-LEV"+nlev):"")+roiTag;
......
......@@ -68,32 +68,36 @@ public class CuasDnnRemote implements AutoCloseable {
return counts;
}
/** One INFER result: full-frame offset5 {dx,dy,s,Vx,Vy}[5][H*W] (plane-major) +
* ROI field [rh*rw][nvel] (softmax*s, pixel-major) + the GPU compute ms. */
public static class Result {
/** Batched INFER result for `count` scenes (newest = start + s*stride): full-frame offset5
* {dx,dy,s,Vx,Vy}[count][5][H*W] + ROI field [count][rh*rw][nvel] (softmax*s, ghostbusted on
* the GPU) + the total pure-GPU compute ms (continuous = production throughput). */
public static class BatchResult {
public double gpuMs;
public int H, W, rh, rw, nvel;
public float [][] offset5; // [5][H*W]
public float [][] roiField; // [rh*rw][nvel]
public int H, W, count, nvel, rh, rw;
public float [][][] offset5; // [count][5][H*W]
public float [][][] roiField; // [count][rh*rw][nvel]
}
public Result infer(int level, int newest, Rectangle roi) throws Exception {
out.writeInt(CMD_INFER); out.writeInt(level); out.writeInt(newest);
/** Infer `count` scenes of a level in one round-trip (newest_s = start + s*stride). rmaxCells>0
* enables the on-GPU ghostbuster (== CuasDetectRT.dnnGhostbust). Keep `count` modest so the
* reply byte[] stays < 2GB (count*5*H*W*4): count<=64 is ~419MB at 640x512. */
public BatchResult inferBatch(int level, int start, int count, int stride, Rectangle roi, double rmaxCells) throws Exception {
out.writeInt(CMD_INFER); out.writeInt(level); out.writeInt(start); out.writeInt(count); out.writeInt(stride);
out.writeInt(roi.x); out.writeInt(roi.y); out.writeInt(roi.width); out.writeInt(roi.height);
out.writeDouble(rmaxCells);
out.flush();
Result r = new Result();
BatchResult r = new BatchResult();
r.gpuMs = in.readDouble(); r.H = in.readInt(); r.W = in.readInt();
int hw = r.H * r.W;
byte [] ob = new byte [5 * hw * 4]; in.readFully(ob);
r.count = in.readInt(); r.nvel = in.readInt(); r.rh = in.readInt(); r.rw = in.readInt();
int hw = r.H * r.W, rn = r.rh * r.rw;
byte [] ob = new byte [r.count * 5 * hw * 4]; in.readFully(ob);
ByteBuffer obb = ByteBuffer.wrap(ob);
r.offset5 = new float [5][hw];
for (int c = 0; c < 5; c++) for (int p = 0; p < hw; p++) r.offset5[c][p] = obb.getFloat();
r.rh = in.readInt(); r.rw = in.readInt(); r.nvel = in.readInt();
int rn = r.rh * r.rw;
byte [] rb = new byte [rn * r.nvel * 4]; in.readFully(rb);
r.offset5 = new float [r.count][5][hw];
for (int s = 0; s < r.count; s++) for (int c = 0; c < 5; c++) for (int p = 0; p < hw; p++) r.offset5[s][c][p] = obb.getFloat();
byte [] rb = new byte [r.count * rn * r.nvel * 4]; in.readFully(rb);
ByteBuffer rbb = ByteBuffer.wrap(rb);
r.roiField = new float [rn][r.nvel];
for (int p = 0; p < rn; p++) for (int v = 0; v < r.nvel; v++) r.roiField[p][v] = rbb.getFloat();
r.roiField = new float [r.count][rn][r.nvel];
for (int s = 0; s < r.count; s++) for (int p = 0; p < rn; p++) for (int v = 0; v < r.nvel; v++) r.roiField[s][p][v] = rbb.getFloat();
return r;
}
......
This diff is collapsed.
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