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 { ...@@ -728,21 +728,29 @@ public class CuasDetectRT {
double [][][] off5 = new double [5][num][H*W]; // {dx,dy,s,Vx,Vy} full-frame -> -OFFSET double [][][] off5 = new double [5][num][H*W]; // {dx,dy,s,Vx,Vy} full-frame -> -OFFSET
String [] ts_dnn = new String [num]; 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); 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++) { // batched: request the level's in-window scenes in chunks; the DGX runs them continuously
int newest = (w0 + j)*dnn_stride + N_dnn - 1; // newest frame index in this level (same as local) // (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(); 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(); long t1 = System.currentTimeMillis();
double [][][] fld = new double [rnp][1][r.nvel]; for (int jj = 0; jj < cnt; jj++) {
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]; } int j = j0 + jj;
// ghostbuster: zero untrained corner-velocity cells + ghost-peak pixels, matching the local CPU path double [][][] fld = new double [rnp][1][br.nvel];
// (the DGX returns the raw softmax*s field) - removes the corner sidelobe noise. By Claude on 06/20/2026 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]; }
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);
dnn_roi[j] = fld; 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; 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 roiTag = "-ROI"+roi.x+"_"+roi.y+"_"+roi.width+"_"+roi.height;
String title = title_conv5d+"-DNN"+((nlev>0)?("-LEV"+nlev):"")+roiTag; String title = title_conv5d+"-DNN"+((nlev>0)?("-LEV"+nlev):"")+roiTag;
......
...@@ -68,32 +68,36 @@ public class CuasDnnRemote implements AutoCloseable { ...@@ -68,32 +68,36 @@ public class CuasDnnRemote implements AutoCloseable {
return counts; return counts;
} }
/** One INFER result: full-frame offset5 {dx,dy,s,Vx,Vy}[5][H*W] (plane-major) + /** Batched INFER result for `count` scenes (newest = start + s*stride): full-frame offset5
* ROI field [rh*rw][nvel] (softmax*s, pixel-major) + the GPU compute ms. */ * {dx,dy,s,Vx,Vy}[count][5][H*W] + ROI field [count][rh*rw][nvel] (softmax*s, ghostbusted on
public static class Result { * the GPU) + the total pure-GPU compute ms (continuous = production throughput). */
public static class BatchResult {
public double gpuMs; public double gpuMs;
public int H, W, rh, rw, nvel; public int H, W, count, nvel, rh, rw;
public float [][] offset5; // [5][H*W] public float [][][] offset5; // [count][5][H*W]
public float [][] roiField; // [rh*rw][nvel] public float [][][] roiField; // [count][rh*rw][nvel]
} }
public Result infer(int level, int newest, Rectangle roi) throws Exception { /** Infer `count` scenes of a level in one round-trip (newest_s = start + s*stride). rmaxCells>0
out.writeInt(CMD_INFER); out.writeInt(level); out.writeInt(newest); * 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.writeInt(roi.x); out.writeInt(roi.y); out.writeInt(roi.width); out.writeInt(roi.height);
out.writeDouble(rmaxCells);
out.flush(); out.flush();
Result r = new Result(); BatchResult r = new BatchResult();
r.gpuMs = in.readDouble(); r.H = in.readInt(); r.W = in.readInt(); r.gpuMs = in.readDouble(); r.H = in.readInt(); r.W = in.readInt();
int hw = r.H * r.W; r.count = in.readInt(); r.nvel = in.readInt(); r.rh = in.readInt(); r.rw = in.readInt();
byte [] ob = new byte [5 * hw * 4]; in.readFully(ob); 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); ByteBuffer obb = ByteBuffer.wrap(ob);
r.offset5 = new float [5][hw]; r.offset5 = new float [r.count][5][hw];
for (int c = 0; c < 5; c++) for (int p = 0; p < hw; p++) r.offset5[c][p] = obb.getFloat(); 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();
r.rh = in.readInt(); r.rw = in.readInt(); r.nvel = in.readInt(); byte [] rb = new byte [r.count * rn * r.nvel * 4]; in.readFully(rb);
int rn = r.rh * r.rw;
byte [] rb = new byte [rn * r.nvel * 4]; in.readFully(rb);
ByteBuffer rbb = ByteBuffer.wrap(rb); ByteBuffer rbb = ByteBuffer.wrap(rb);
r.roiField = new float [rn][r.nvel]; r.roiField = new float [r.count][rn][r.nvel];
for (int p = 0; p < rn; p++) for (int v = 0; v < r.nvel; v++) r.roiField[p][v] = rbb.getFloat(); 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; return r;
} }
......
#!/usr/bin/env python3 #!/usr/bin/env python3
"""DGX remote-inference server for the CUAS RawFCN (stateful). By Claude on 06/20/2026. """DGX remote-inference server for the CUAS RawFCN (stateful, batched). By Claude on 06/20/2026.
Java uploads the SUBAVG+LoG-conditioned stack ONCE; the DGX builds the temporal pyramid Java uploads the SUBAVG+LoG-conditioned (optionally synth-mixed) stack ONCE; the DGX builds the
(the 0.5*(now+prev) averaging, exactly as Java's temporalAverageLReLU), then serves temporal pyramid (0.5*(now+prev), as Java's temporalAverageLReLU), then serves BATCHED full-res
full-resolution dense inference per (level, scene): shift-and-stitch -> decode on GPU -> inference: one INFER does a whole range of scenes of a level (chunked for GPU memory), shift-and-
return only the FULL-FRAME offset {dx,dy,s,Vx,Vy} + the ROI-only 121-cell softmax*s field. stitch -> on-GPU GHOSTBUSTER (== CuasDetectRT.dnnGhostbust) -> decode -> returns the full-frame
A debug READBACK returns one conditioned pyramid frame for inspection. Runs inside offset {dx,dy,s,Vx,Vy} + the ROI-only 121-cell softmax*s, per scene. A debug READBACK returns one
nvcr.io/nvidia/pytorch:25.10-py3. conditioned pyramid frame. Runs inside nvcr.io/nvidia/pytorch:25.10-py3.
Protocol (big-endian, matches Java DataInput/OutputStream). Each request: int32 cmd, then: Protocol (big-endian, matches Java DataInput/OutputStream). Each request: int32 cmd, then:
UPLOAD (1): int32 T,H,W ; T*H*W float32 (conditioned stack) UPLOAD (1): int32 T,H,W ; T*H*W float32 (conditioned stack)
-> reply: int32 n_levels ; n_levels x int32 frames_per_level ; float64 build_ms -> reply: int32 n_levels ; n_levels x int32 frames_per_level ; int32 N ; float64 build_ms
INFER (2): int32 level, newest, roi_x, roi_y, roi_w, roi_h INFER (2): int32 level, start, count, stride, roi_x, roi_y, roi_w, roi_h ; float64 rmax_cells
-> reply: float64 gpu_ms ; int32 H,W ; 5*H*W float32 (offset5: dx,dy,s,Vx,Vy, plane-major) (count scenes: newest = start + j*stride, j=0..count-1; rmax_cells<=0 disables ghostbuster)
int32 rh,rw,nvel ; rh*rw*nvel float32 (ROI softmax*s, pixel-major) -> reply: float64 gpu_ms ; int32 H,W,count,nvel,rh,rw
READBACK(3): int32 level, frame -> reply: int32 H,W ; H*W float32 (one conditioned frame) count*5*H*W float32 (offset5 dx,dy,s,Vx,Vy, plane-major per scene)
count*rh*rw*nvel float32 (ROI softmax*s, pixel-major per scene)
READBACK(3): int32 level, frame -> reply: int32 H,W ; H*W float32
BYE (0): close BYE (0): close
""" """
import argparse, os, socket, struct, time import argparse, os, socket, struct, time
...@@ -25,6 +27,7 @@ import torch.nn.functional as F ...@@ -25,6 +27,7 @@ import torch.nn.functional as F
from model import RawFCN from model import RawFCN
CMD_BYE, CMD_UPLOAD, CMD_INFER, CMD_READBACK = 0, 1, 2, 3 CMD_BYE, CMD_UPLOAD, CMD_INFER, CMD_READBACK = 0, 1, 2, 3
GPU_CHUNK = 16 # scenes processed per batched GPU pass (memory vs utilization)
def load_model(run_dir, device): def load_model(run_dir, device):
...@@ -54,58 +57,60 @@ def recvall(conn, n): ...@@ -54,58 +57,60 @@ def recvall(conn, n):
def build_pyramid(log, n_levels_max=8): def build_pyramid(log, n_levels_max=8):
# Replicates Java's pyramid (CuasDetectRT.java:824-883, temporalAverageLReLU linear). # Replicates Java's pyramid (CuasDetectRT temporalAverageLReLU, linear). log: [T,H,W].
# log: [T,H,W] LoG-conditioned frames. levels = [0.5 * (log[1:] + log[:-1])] # [T-1,H,W]
# level0[i] = 0.5*(log[i+1] + log[i]) i=0..T-2 (slice-as-shifted-view add)
# level L+1[i]= 0.5*(levelL[2i+2] + levelL[2i]) i=0..len(L)//2-2
levels = [0.5 * (log[1:] + log[:-1])] # [T-1, H, W]
while len(levels) < n_levels_max: while len(levels) < n_levels_max:
prev = levels[-1] # [Tl, H, W] prev = levels[-1]
nl = len(prev) // 2 - 1 nl = len(prev) // 2 - 1
if nl < 1: if nl < 1:
break break
idx = torch.arange(nl, device=log.device) # [nl] idx = torch.arange(nl, device=log.device)
levels.append(0.5 * (prev[2 * idx + 2] + prev[2 * idx])) # [nl, H, W] levels.append(0.5 * (prev[2 * idx + 2] + prev[2 * idx]))
return levels return levels
@torch.no_grad() @torch.no_grad()
def shift_stitch(m, x, P, S=4): def shift_stitch(m, x, P, S=4):
# FULL-RES field via shift-and-stitch (validated == per-pixel in fp64, dense_check.py). # x: [B,N,H,W] -> full-res field [B,C,H,W] (validated == per-pixel in fp64, dense_check.py).
# x: [N,H,W]. Pad by half so dense cell (oi,oj) aligns to input pixel (S*oi,S*oj); the B, N, H, W = x.shape
# S*S shifts interleave into [C,H,W].
N, H, W = x.shape
half = P // 2 half = P // 2
xp = F.pad(x, (half, half, half, half)) # [N, H+P, W+P] xp = F.pad(x, (half, half, half, half)) # [B,N,H+P,W+P]
full = torch.zeros(m.out_ch, H, W, device=x.device, dtype=x.dtype) full = torch.zeros(B, m.out_ch, H, W, device=x.device, dtype=x.dtype)
for sy in range(S): for sy in range(S):
for sx in range(S): for sx in range(S):
y = m(xp[:, sy:, sx:][None])[0] # [C, oH, oW] y = m(xp[:, :, sy:, sx:]) # [B,C,oH,oW]
oh = min(y.shape[1], (H - sy + S - 1) // S) oh = min(y.shape[2], (H - sy + S - 1) // S)
ow = min(y.shape[2], (W - sx + S - 1) // S) ow = min(y.shape[3], (W - sx + S - 1) // S)
full[:, sy::S, sx::S] = y[:, :oh, :ow] full[:, :, sy::S, sx::S] = y[:, :, :oh, :ow]
return full # [C, H, W] return full # [B,C,H,W]
@torch.no_grad() @torch.no_grad()
def decode(field, vr, roi): def decode(field, vr, roi, rmax_cells):
# field: [C,H,W] full-res. C = 1(det) + nvel + 2(dx,dy). Decode on GPU. # field: [B,C,H,W]. On-GPU ghostbuster (== CuasDetectRT.dnnGhostbust) + decode.
vdim = 2 * vr + 1 vdim = 2 * vr + 1
nvel = vdim * vdim nvel = vdim * vdim
C, H, W = field.shape B, C, H, W = field.shape
s = field[0].sigmoid() # [H,W] detection confidence
p = field[1:1 + nvel].softmax(0) # [nvel,H,W] velocity softmax
# cell k -> (vx,vy) = (k%vdim - vr, k//vdim - vr); centroid = sum_k p_k * coord_k
k = torch.arange(nvel, device=field.device)
cellx = (k % vdim - vr).to(field.dtype) # [nvel]
celly = (k // vdim - vr).to(field.dtype)
vx = (p * cellx[:, None, None]).sum(0) # [H,W] velocity centroid (cells)
vy = (p * celly[:, None, None]).sum(0)
dx, dy = field[1 + nvel], field[1 + nvel + 1] # [H,W]
offset5 = torch.stack([dx, dy, s, vx, vy]) # [5,H,W] -> -OFFSET (full frame)
x0, y0, rw, rh = roi x0, y0, rw, rh = roi
roi_field = (p * s[None])[:, y0:y0 + rh, x0:x0 + rw] # [nvel,rh,rw] softmax*s over ROI s = field[:, 0].sigmoid() # [B,H,W]
return offset5, roi_field.permute(1, 2, 0).contiguous(), nvel # offset5 [5,H,W], roi [rh,rw,nvel] p = field[:, 1:1 + nvel].softmax(1) # [B,nvel,H,W]
k = torch.arange(nvel, device=field.device)
cx = (k % vdim - vr).to(field.dtype) # [nvel] vx cell coord
cy = (k // vdim - vr).to(field.dtype) # [nvel] vy cell coord
if rmax_cells > 0: # ghostbuster
corner = (cx * cx + cy * cy) > (rmax_cells * rmax_cells) # [nvel] untrained corner cells
ghost = corner[p.argmax(1)] # [B,H,W] peak lands in a corner -> whole pixel is a ghost
p = p * (~corner).to(p.dtype)[None, :, None, None] # zero corner cells everywhere
keep = (~ghost).to(p.dtype) # [B,H,W]
p = p * keep[:, None] # zero all cells at ghost pixels
s = s * keep # s=0 at ghost pixels
psum = p.sum(1).clamp_min(1e-12) # [B,H,W] (normalize the centroid)
vx = (p * cx[None, :, None, None]).sum(1) / psum # [B,H,W] velocity centroid (cells)
vy = (p * cy[None, :, None, None]).sum(1) / psum
dx, dy = field[:, 1 + nvel], field[:, 1 + nvel + 1] # [B,H,W]
offset5 = torch.stack([dx, dy, s, vx, vy], 1) # [B,5,H,W] -> -OFFSET
roi_field = (p * s[:, None])[:, :, y0:y0 + rh, x0:x0 + rw] # [B,nvel,rh,rw] softmax*s over ROI
return offset5, roi_field.permute(0, 2, 3, 1).contiguous(), nvel # [B,5,H,W], [B,rh,rw,nvel]
def serve(run_dir, host, port): def serve(run_dir, host, port):
...@@ -114,7 +119,7 @@ def serve(run_dir, host, port): ...@@ -114,7 +119,7 @@ def serve(run_dir, host, port):
m, N, P, vr = load_model(run_dir, device) m, N, P, vr = load_model(run_dir, device)
print(f"device={device} gpu={torch.cuda.get_device_name(0) if device=='cuda' else 'cpu'} " print(f"device={device} gpu={torch.cuda.get_device_name(0) if device=='cuda' else 'cpu'} "
f"patch={P} N={N} vr={vr}", flush=True) f"patch={P} N={N} vr={vr}", flush=True)
_ = shift_stitch(m, torch.zeros(N, 64, 64, device=device), P) # warm-up _ = shift_stitch(m, torch.zeros(1, N, 64, 64, device=device), P) # warm-up
if device == "cuda": if device == "cuda":
torch.cuda.synchronize() torch.cuda.synchronize()
print("warm-up done", flush=True) print("warm-up done", flush=True)
...@@ -123,8 +128,8 @@ def serve(run_dir, host, port): ...@@ -123,8 +128,8 @@ def serve(run_dir, host, port):
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((host, port)) srv.bind((host, port))
srv.listen(4) srv.listen(4)
print(f"listening on {host}:{port} (N={N}, full-res shift-and-stitch, stateful)", flush=True) print(f"listening on {host}:{port} (N={N}, batched full-res shift-and-stitch + ghostbuster)", flush=True)
pyr = None # uploaded pyramid (per connection) pyr = None
while True: while True:
conn, addr = srv.accept() conn, addr = srv.accept()
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
...@@ -148,32 +153,45 @@ def serve(run_dir, host, port): ...@@ -148,32 +153,45 @@ def serve(run_dir, host, port):
print(f"{datetime.now():%H:%M:%S} UPLOAD T={T} {H}x{W} -> {nl} levels " print(f"{datetime.now():%H:%M:%S} UPLOAD T={T} {H}x{W} -> {nl} levels "
f"{[len(l) for l in pyr]} build={bms:.1f}ms ({T*H*W*4/1e6:.1f}MB)", flush=True) f"{[len(l) for l in pyr]} build={bms:.1f}ms ({T*H*W*4/1e6:.1f}MB)", flush=True)
conn.sendall(struct.pack(">i", nl) + b"".join(struct.pack(">i", len(l)) for l in pyr) conn.sendall(struct.pack(">i", nl) + b"".join(struct.pack(">i", len(l)) for l in pyr)
+ struct.pack(">id", N, bms)) # +N so the Java client can size scene windows + struct.pack(">id", N, bms))
elif cmd == CMD_INFER: elif cmd == CMD_INFER:
level, newest, rx, ry, rw, rh = struct.unpack(">iiiiii", recvall(conn, 24)) level, start, count, stride, rx, ry, rw, rh = struct.unpack(">iiiiiiii", recvall(conn, 32))
rmax = struct.unpack(">d", recvall(conn, 8))[0]
lev = pyr[level] # [Tl,H,W] lev = pyr[level] # [Tl,H,W]
# newest-FIRST window (channel 0 = newest), matching Java inferROI window[h]=frames[newest-h] H, W = lev.shape[1], lev.shape[2]
# and the training order; .flip(0) reverses the ascending slice. By Claude on 06/20/2026 nvel = (2 * vr + 1) ** 2
win = lev[newest - N + 1:newest + 1].flip(0) # [N,H,W] o5_gpu, rf_gpu = [], []
# Time PURE GPU compute (shift-and-stitch + decode), continuous over the whole range -
# the production throughput. Results stay on-GPU (prod feeds Layer 2 there); the D2H copy
# below is dev-only and NOT timed. By Claude on 06/20/2026
if device == "cuda": if device == "cuda":
ev0 = torch.cuda.Event(enable_timing=True); ev1 = torch.cuda.Event(enable_timing=True) ev0 = torch.cuda.Event(enable_timing=True); ev1 = torch.cuda.Event(enable_timing=True)
torch.cuda.synchronize(); ev0.record() torch.cuda.synchronize(); ev0.record()
field = shift_stitch(m, win, P) # [C,H,W] else:
offset5, roi_field, nvel = decode(field, vr, (rx, ry, rw, rh)) t0 = time.perf_counter()
for c0 in range(0, count, GPU_CHUNK):
b = min(GPU_CHUNK, count - c0)
# newest-first windows (channel 0 = newest), matching the Java order
wins = torch.stack([lev[(start + (c0 + j) * stride) - N + 1:
(start + (c0 + j) * stride) + 1].flip(0) for j in range(b)]) # [b,N,H,W]
field = shift_stitch(m, wins, P) # [b,C,H,W]
o5, rf, nv = decode(field, vr, (rx, ry, rw, rh), rmax)
nvel = nv
o5_gpu.append(o5); rf_gpu.append(rf) # keep on GPU
if device == "cuda": if device == "cuda":
ev1.record(); torch.cuda.synchronize(); gms = ev0.elapsed_time(ev1) ev1.record(); torch.cuda.synchronize(); gms = ev0.elapsed_time(ev1)
else: else:
gms = 0.0 gms = (time.perf_counter() - t0) * 1e3
H, W = field.shape[1], field.shape[2] allo = torch.cat(o5_gpu, 0).cpu().numpy().astype(">f4") # [count,5,H,W] D2H untimed (dev-only)
o5 = offset5.cpu().numpy().astype(">f4") # [5,H,W] allr = torch.cat(rf_gpu, 0).cpu().numpy().astype(">f4") # [count,rh,rw,nvel]
rf = roi_field.cpu().numpy().astype(">f4") # [rh,rw,nvel] print(f"{datetime.now():%H:%M:%S} INFER lev={level} {count} scenes (f{start}..,stride {stride}) "
print(f"{datetime.now():%H:%M:%S} INFER lev={level} f={newest} ROI={rw}x{rh} " f"ROI={rw}x{rh} ghost={rmax:.1f} gpu={gms:.1f}ms ({(allo.nbytes+allr.nbytes)/1e6:.1f}MB out)", flush=True)
f"gpu_exec={gms:.1f}ms ({(o5.nbytes+rf.nbytes)/1e6:.1f}MB out)", flush=True) conn.sendall(struct.pack(">diiiiii", gms, H, W, count, nvel, rh, rw))
conn.sendall(struct.pack(">dii", gms, H, W) + o5.tobytes() conn.sendall(allo.tobytes())
+ struct.pack(">iii", rh, rw, nvel) + rf.tobytes()) conn.sendall(allr.tobytes())
elif cmd == CMD_READBACK: elif cmd == CMD_READBACK:
level, frame = struct.unpack(">ii", recvall(conn, 8)) level, frame = struct.unpack(">ii", recvall(conn, 8))
fr = pyr[level][frame].cpu().numpy().astype(">f4") # [H,W] fr = pyr[level][frame].cpu().numpy().astype(">f4")
print(f"{datetime.now():%H:%M:%S} READBACK lev={level} f={frame}", flush=True) print(f"{datetime.now():%H:%M:%S} READBACK lev={level} f={frame}", flush=True)
conn.sendall(struct.pack(">ii", fr.shape[0], fr.shape[1]) + fr.tobytes()) conn.sendall(struct.pack(">ii", fr.shape[0], fr.shape[1]) + fr.tobytes())
except (ConnectionError, struct.error, IndexError) as ex: except (ConnectionError, struct.error, IndexError) as ex:
......
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