Merge branch 'merge-layout' into rc175: merge engine observations as a packed 16-byte record, the scale preparation folded into the per-frame fit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -677,8 +677,9 @@ size_t RotationScaleMerge::DeviceBytes() const {
|
||||
// What each costs at the peak, measured: an observation's arrays and its group CSR; a full's arrays
|
||||
// with the surface terms, or with the outlier test's and the error model's samples; an hkl's raw run
|
||||
// and its ASU group's sums. The events are counted on the device path's ingest; elsewhere every
|
||||
// observation may be an event of its own.
|
||||
constexpr size_t BYTES_PER_OBS = 80, BYTES_PER_EVENT = 260, BYTES_PER_HKL = 200;
|
||||
// observation may be an event of its own. Per observation, 13 bytes less than measured: the
|
||||
// device scaling no longer keeps 1/sigma, the fit coefficient and its flag per observation.
|
||||
constexpr size_t BYTES_PER_OBS = 67, BYTES_PER_EVENT = 260, BYTES_PER_HKL = 200;
|
||||
const auto events = static_cast<size_t>(resident_ingest ? rocking_events_at_ingest : n_partials_obs);
|
||||
const size_t bytes = BYTES_PER_OBS * static_cast<size_t>(n_partials_obs) + BYTES_PER_EVENT * events
|
||||
+ BYTES_PER_HKL * rawrun_start.size();
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace {
|
||||
return s[0];
|
||||
}
|
||||
|
||||
// The four immutable fields of an observation that the scaling, the combine and the grouping read
|
||||
// together, in one 16-byte record: the kernels that reach an observation through a permutation
|
||||
// (the group reduction above all) then fetch one sector for it instead of one per field.
|
||||
struct alignas(16) ObsRecord { float I, sigma, partiality, prescaling_corr; }; // floats 0, 1, 2, 3
|
||||
|
||||
// The observation's inverse-variance weight in the scaling reference (0 = not in it), and its
|
||||
// scaled intensity. The same filter as the host's ReferenceWeight.
|
||||
__device__ __forceinline__ double ReferenceWeight(float I, float sigma, float partiality, float corr,
|
||||
@@ -72,8 +77,7 @@ namespace {
|
||||
const int32_t *__restrict__ group_perm,
|
||||
const int32_t *__restrict__ group_start,
|
||||
const int32_t *__restrict__ group_count,
|
||||
const float *__restrict__ I, const float *__restrict__ sigma,
|
||||
const float *__restrict__ partiality,
|
||||
const ObsRecord *__restrict__ obs,
|
||||
const float *__restrict__ corr,
|
||||
double *__restrict__ group_mean) {
|
||||
for (int g = blockIdx.x * blockDim.x + threadIdx.x; g < n_groups; g += gridDim.x * blockDim.x) {
|
||||
@@ -81,8 +85,9 @@ namespace {
|
||||
double sw = 0.0, swI = 0.0;
|
||||
for (int p = lo; p < hi; ++p) {
|
||||
const int i = group_perm[p];
|
||||
const ObsRecord o = obs[i];
|
||||
float I_corr;
|
||||
const double w = ReferenceWeight(I[i], sigma[i], partiality[i], corr[i], min_partiality, I_corr);
|
||||
const double w = ReferenceWeight(o.I, o.sigma, o.partiality, corr[i], min_partiality, I_corr);
|
||||
if (w <= 0.0) continue;
|
||||
sw += w;
|
||||
swI += w * I_corr;
|
||||
@@ -91,29 +96,20 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// Per-observation scale-fit coefficient (rotation model) and accept flag, recomputed each scaling
|
||||
// iteration once the group means are known. coeff = partiality * (1/prescaling_corr) * mean[group].
|
||||
__global__ void PrepScaleObsKernel(int n_obs, double min_partiality, const int32_t *__restrict__ group,
|
||||
const float *__restrict__ partiality, const float *__restrict__ prescaling_corr,
|
||||
const float *__restrict__ zeta, const uint8_t *__restrict__ on_ice,
|
||||
const double *__restrict__ group_mean,
|
||||
const float *__restrict__ sigma, double *__restrict__ inv_sigma,
|
||||
float *__restrict__ sco_coeff, uint8_t *__restrict__ sco_ok) {
|
||||
const int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= n_obs) return;
|
||||
// The scale-fit coefficient of an observation (rotation model), recomputed each scaling iteration
|
||||
// once the group means are known: coeff = partiality * (1/prescaling_corr) * mean[group], or false
|
||||
// where the observation is not one the reference is built from (the host FitPerFrameG says why).
|
||||
__device__ __forceinline__ bool ScaleCoeff(int i, double min_partiality, const int32_t *__restrict__ group,
|
||||
const ObsRecord &o, const float *__restrict__ zeta,
|
||||
const uint8_t *__restrict__ on_ice,
|
||||
const double *__restrict__ group_mean, float &coeff) {
|
||||
const int g = group[i];
|
||||
// Only the observations the reference is built from (the host FitPerFrameG says why).
|
||||
bool ok = (g >= 0) && !on_ice[i] && isfinite(zeta[i]) && zeta[i] > 0.0f && partiality[i] >= min_partiality;
|
||||
double mean = 0.0;
|
||||
if (ok) {
|
||||
mean = group_mean[g];
|
||||
ok = isfinite(mean);
|
||||
}
|
||||
// sigma never changes once it is uploaded, so its reciprocal is the same in every scaling
|
||||
// pass; hoisted as the CPU does (ScaleObs::weight).
|
||||
inv_sigma[i] = SafeInvD(sigma[i], 1.0);
|
||||
sco_ok[i] = ok ? 1 : 0;
|
||||
sco_coeff[i] = ok ? float(double(partiality[i]) * SafeInvD(prescaling_corr[i], 1.0) * mean) : 0.0f;
|
||||
if (!((g >= 0) && !on_ice[i] && isfinite(zeta[i]) && zeta[i] > 0.0f && o.partiality >= min_partiality))
|
||||
return false;
|
||||
const double mean = group_mean[g];
|
||||
if (!isfinite(mean)) return false;
|
||||
coeff = float(double(o.partiality) * SafeInvD(o.prescaling_corr, 1.0) * mean);
|
||||
return true;
|
||||
}
|
||||
|
||||
// One block per frame: the per-frame scale G as the weighted least-squares slope over the frame's
|
||||
@@ -122,13 +118,15 @@ namespace {
|
||||
// for the partials, whose arrays are already frame-contiguous) maps a position in the frame's
|
||||
// [lo,hi) range to the obs index, so the same kernel scales the fulls (emit-ordered) through a
|
||||
// frame-grouping permutation without physically reordering the fulls arrays. info[f] receives the
|
||||
// fit's information on G, sum w^2 c^2 (0 for a frame left alone).
|
||||
// fit's information on G, sum w^2 c^2 (0 for a frame left alone). The weight is 1/sigma, as the
|
||||
// CPU's ScaleObs::weight.
|
||||
__global__ void FitPerFrameGKernel(int n_frames,
|
||||
const int32_t *__restrict__ frame_start,
|
||||
const int32_t *__restrict__ frame_count,
|
||||
const float *__restrict__ I, const double *__restrict__ inv_sigma,
|
||||
const float *__restrict__ sco_coeff, const uint8_t *__restrict__ sco_ok,
|
||||
const int32_t *__restrict__ perm, long min_obs,
|
||||
const int32_t *__restrict__ perm, double min_partiality,
|
||||
const int32_t *__restrict__ group, const ObsRecord *__restrict__ obs,
|
||||
const float *__restrict__ zeta, const uint8_t *__restrict__ on_ice,
|
||||
const double *__restrict__ group_mean, long min_obs,
|
||||
double *__restrict__ g, uint8_t *__restrict__ scaled,
|
||||
double *__restrict__ info) {
|
||||
const int f = blockIdx.x;
|
||||
@@ -137,25 +135,28 @@ namespace {
|
||||
__shared__ double sh[BLK];
|
||||
if (threadIdx.x == 0) info[f] = 0.0;
|
||||
|
||||
// The sums are formed in the same pass as the count; a frame with too few observations
|
||||
// discards them.
|
||||
long cnt_local = 0;
|
||||
for (int i = lo + threadIdx.x; i < hi; i += blockDim.x)
|
||||
if (sco_ok[perm ? perm[i] : i]) ++cnt_local;
|
||||
double num = 0.0, den = 0.0;
|
||||
for (int i = lo + threadIdx.x; i < hi; i += blockDim.x) {
|
||||
const int a = perm ? perm[i] : i;
|
||||
const ObsRecord o = obs[a];
|
||||
float coeff_f;
|
||||
if (!ScaleCoeff(a, min_partiality, group, o, zeta, on_ice, group_mean, coeff_f)) continue;
|
||||
++cnt_local;
|
||||
const double coeff = coeff_f;
|
||||
const double w = SafeInvD(o.sigma, 1.0);
|
||||
const double w2 = w * w;
|
||||
num += w2 * coeff * double(o.I);
|
||||
den += w2 * coeff * coeff;
|
||||
}
|
||||
const double cnt = BlockReduceSum(double(cnt_local), sh);
|
||||
__shared__ double s_cnt;
|
||||
if (threadIdx.x == 0) s_cnt = cnt;
|
||||
__syncthreads();
|
||||
if (s_cnt < min_obs) return; // leave g[f]/scaled[f] as-is
|
||||
|
||||
double num = 0.0, den = 0.0;
|
||||
for (int i = lo + threadIdx.x; i < hi; i += blockDim.x) {
|
||||
const int a = perm ? perm[i] : i;
|
||||
if (!sco_ok[a]) continue;
|
||||
const double coeff = sco_coeff[a];
|
||||
const double w = inv_sigma[a];
|
||||
const double w2 = w * w;
|
||||
num += w2 * coeff * double(I[a]);
|
||||
den += w2 * coeff * coeff;
|
||||
}
|
||||
const double tnum = BlockReduceSum(num, sh); __syncthreads();
|
||||
const double tden = BlockReduceSum(den, sh);
|
||||
if (threadIdx.x == 0) {
|
||||
@@ -172,8 +173,7 @@ namespace {
|
||||
__global__ void PerFrameCCKernel(int n_frames, double min_partiality,
|
||||
const int32_t *__restrict__ frame_start,
|
||||
const int32_t *__restrict__ frame_count,
|
||||
const float *__restrict__ I, const float *__restrict__ sigma,
|
||||
const float *__restrict__ partiality, const float *__restrict__ corr,
|
||||
const ObsRecord *__restrict__ obs, const float *__restrict__ corr,
|
||||
const uint8_t *__restrict__ on_ice, const int32_t *__restrict__ group,
|
||||
const double *__restrict__ group_mean,
|
||||
double *__restrict__ cc_out, int64_t *__restrict__ cc_n_out) {
|
||||
@@ -187,13 +187,14 @@ namespace {
|
||||
if (on_ice[i]) continue;
|
||||
const int g = group[i];
|
||||
if (g < 0) continue;
|
||||
if (partiality[i] < min_partiality) continue;
|
||||
const ObsRecord o = obs[i];
|
||||
if (o.partiality < min_partiality) continue;
|
||||
const float c = corr[i];
|
||||
if (!isfinite(I[i]) || !isfinite(c) || !(c > 0.0f)) continue;
|
||||
if (!isfinite(sigma[i]) || !(sigma[i] > 0.0f)) continue;
|
||||
if (!isfinite(o.I) || !isfinite(c) || !(c > 0.0f)) continue;
|
||||
if (!isfinite(o.sigma) || !(o.sigma > 0.0f)) continue;
|
||||
const double mean = group_mean[g];
|
||||
if (!isfinite(mean)) continue;
|
||||
const double img = double(I[i]) * c;
|
||||
const double img = double(o.I) * c;
|
||||
sx += img; sy += mean; sx2 += img * img; sy2 += mean * mean; sxy += img * mean; ++nl;
|
||||
}
|
||||
const double tsx = BlockReduceSum(sx, sh); __syncthreads();
|
||||
@@ -250,15 +251,16 @@ namespace {
|
||||
|
||||
// corr = prescaling_corr / (partiality * G[frame]) for fitted frames; unchanged otherwise (grid-stride).
|
||||
__global__ void UpdateCorrKernel(int n_obs, const int32_t *__restrict__ frame,
|
||||
const float *__restrict__ prescaling_corr, const float *__restrict__ partiality,
|
||||
const ObsRecord *__restrict__ obs,
|
||||
const double *__restrict__ g, const uint8_t *__restrict__ scaled,
|
||||
float *__restrict__ corr) {
|
||||
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n_obs; i += gridDim.x * blockDim.x) {
|
||||
const int f = frame[i];
|
||||
if (!scaled[f]) continue;
|
||||
const double denom = double(partiality[i]) * g[f];
|
||||
corr[i] = (isfinite(double(prescaling_corr[i])) && isfinite(denom) && denom > 0.0)
|
||||
? float(prescaling_corr[i] / denom) : NAN;
|
||||
const ObsRecord o = obs[i];
|
||||
const double denom = double(o.partiality) * g[f];
|
||||
corr[i] = (isfinite(double(o.prescaling_corr)) && isfinite(denom) && denom > 0.0)
|
||||
? float(o.prescaling_corr / denom) : NAN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,11 +271,10 @@ namespace {
|
||||
__device__ __forceinline__ float Fmax(float a, float b) { return (a < b) ? b : a; }
|
||||
|
||||
// A partial is usable for the combine iff its corr and (I, sigma) are finite with corr>0, sigma>0.
|
||||
__device__ __forceinline__ bool CombineUsable(int i, const float *I, const float *sigma,
|
||||
const float *corr) {
|
||||
__device__ __forceinline__ bool CombineUsable(int i, const ObsRecord *obs, const float *corr) {
|
||||
const float c = corr[i];
|
||||
if (!(c > 0.0f) || !isfinite(c)) return false;
|
||||
return isfinite(I[i]) && isfinite(sigma[i]) && sigma[i] > 0.0f;
|
||||
return isfinite(obs[i].I) && isfinite(obs[i].sigma) && obs[i].sigma > 0.0f;
|
||||
}
|
||||
|
||||
// RotationScaleMerge::OBS_CLIPPED / OBS_OVERLOADED, the flag bits a partial's `clipped` byte carries.
|
||||
@@ -287,8 +288,8 @@ namespace {
|
||||
// RotationScaleMerge::max_frame_gap, passed in rather than recomputed here so that the host
|
||||
// and the device compare the same float and the combine stays bit-identical on both paths.
|
||||
float max_frame_gap;
|
||||
const float *__restrict__ I, *__restrict__ sigma, *__restrict__ corr,
|
||||
*__restrict__ partiality, *__restrict__ bkg, *__restrict__ var_bkg,
|
||||
const ObsRecord *__restrict__ obs;
|
||||
const float *__restrict__ corr, *__restrict__ bkg, *__restrict__ var_bkg,
|
||||
*__restrict__ image_number, *__restrict__ d, *__restrict__ px, *__restrict__ py;
|
||||
const int32_t *__restrict__ frame;
|
||||
const uint8_t *__restrict__ on_ice, *__restrict__ clipped;
|
||||
@@ -317,14 +318,14 @@ namespace {
|
||||
int n_emit = 0, n_overloaded = 0;
|
||||
int cursor = lo;
|
||||
while (cursor < hi) {
|
||||
while (cursor < hi && !CombineUsable(p.perm[cursor], p.I, p.sigma, p.corr)) ++cursor;
|
||||
while (cursor < hi && !CombineUsable(p.perm[cursor], p.obs, p.corr)) ++cursor;
|
||||
if (cursor >= hi) break;
|
||||
const int ev_start = cursor; // first usable position of the event
|
||||
int ev_end = cursor; // last usable position (inclusive), extended below
|
||||
float last_img = p.image_number[p.perm[cursor]];
|
||||
int probe = cursor + 1;
|
||||
while (probe < hi) {
|
||||
while (probe < hi && !CombineUsable(p.perm[probe], p.I, p.sigma, p.corr)) ++probe;
|
||||
while (probe < hi && !CombineUsable(p.perm[probe], p.obs, p.corr)) ++probe;
|
||||
if (probe >= hi) break;
|
||||
const float img = p.image_number[p.perm[probe]];
|
||||
if (img - last_img > p.max_frame_gap) break;
|
||||
@@ -340,16 +341,16 @@ namespace {
|
||||
int n_pool = 0;
|
||||
for (int m = ev_start; m <= ev_end; ++m) {
|
||||
const int i = p.perm[m];
|
||||
if (!CombineUsable(i, p.I, p.sigma, p.corr)) continue;
|
||||
if (!CombineUsable(i, p.obs, p.corr)) continue;
|
||||
const float b = p.bkg[i];
|
||||
if (isfinite(b)) { pooled_bkg += b; ++n_pool; }
|
||||
}
|
||||
pooled_bkg = n_pool > 0 ? pooled_bkg / n_pool : 0.0;
|
||||
|
||||
auto pooled_I = [&](int i) -> double {
|
||||
const double n_bkg = Dmax(0.0, double(p.sigma[i]) * p.sigma[i] - p.I[i])
|
||||
const double n_bkg = Dmax(0.0, double(p.obs[i].sigma) * p.obs[i].sigma - p.obs[i].I)
|
||||
/ Fmax(p.bkg[i], 1.0f);
|
||||
return double(p.I[i]) + n_bkg * (double(p.bkg[i]) - pooled_bkg);
|
||||
return double(p.obs[i].I) + n_bkg * (double(p.bkg[i]) - pooled_bkg);
|
||||
};
|
||||
|
||||
// Pass B: seed F (inverse-variance mean of pooled_I*corr), plus peak / d / on_ice / partiality.
|
||||
@@ -368,14 +369,14 @@ namespace {
|
||||
const bool clipped = flags & OBS_CLIPPED_FLAG;
|
||||
for (int m = ev_start; m <= ev_end; ++m) {
|
||||
const int i = p.perm[m];
|
||||
if (!CombineUsable(i, p.I, p.sigma, p.corr)) continue;
|
||||
const double sigma_corr = double(p.sigma[i]) * p.corr[i];
|
||||
if (!CombineUsable(i, p.obs, p.corr)) continue;
|
||||
const double sigma_corr = double(p.obs[i].sigma) * p.corr[i];
|
||||
const double w = 1.0 / (sigma_corr * sigma_corr);
|
||||
sum_w += w;
|
||||
sum_wI += w * pooled_I(i) * p.corr[i];
|
||||
sum_partiality += p.partiality[i];
|
||||
if (p.partiality[i] > peak_partiality) {
|
||||
peak_partiality = p.partiality[i];
|
||||
sum_partiality += p.obs[i].partiality;
|
||||
if (p.obs[i].partiality > peak_partiality) {
|
||||
peak_partiality = p.obs[i].partiality;
|
||||
peak_outcome = p.frame[i];
|
||||
peak_frame = p.image_number[i];
|
||||
peak_px = p.px[i]; peak_py = p.py[i];
|
||||
@@ -391,10 +392,10 @@ namespace {
|
||||
sum_w = 0.0; sum_wI = 0.0; sum_wb = 0.0; sum_cwb = 0.0;
|
||||
for (int m = ev_start; m <= ev_end; ++m) {
|
||||
const int i = p.perm[m];
|
||||
if (!CombineUsable(i, p.I, p.sigma, p.corr)) continue;
|
||||
if (!CombineUsable(i, p.obs, p.corr)) continue;
|
||||
const double corr = p.corr[i];
|
||||
const double I_corr = pooled_I(i) * corr;
|
||||
const double sigma_corr = double(p.sigma[i]) * corr;
|
||||
const double sigma_corr = double(p.obs[i].sigma) * corr;
|
||||
// The integrator's own non-signal variance (see the host combine).
|
||||
const double bkg_var = corr * corr * (double) p.var_bkg[i];
|
||||
const double a_var = bkg_var > 0.0 ? bkg_var : sigma_corr * sigma_corr;
|
||||
@@ -459,15 +460,17 @@ namespace {
|
||||
// ===== the per-space-group grouping, built on the device from the raw-hkl runs =====
|
||||
|
||||
// The host's finiteness test (finite_ok in RotationScaleMerge::Ingest), on the same uploaded values.
|
||||
__device__ __forceinline__ bool ObsFinite(int i, const float *I, const float *sigma, const float *pcorr) {
|
||||
return isfinite(I[i]) && isfinite(pcorr[i]) && pcorr[i] != 0.0f && isfinite(sigma[i]) && sigma[i] > 0.0f;
|
||||
__device__ __forceinline__ bool ObsFinite(int i, const ObsRecord *obs) {
|
||||
const ObsRecord o = obs[i];
|
||||
return isfinite(o.I) && isfinite(o.prescaling_corr) && o.prescaling_corr != 0.0f && isfinite(o.sigma)
|
||||
&& o.sigma > 0.0f;
|
||||
}
|
||||
|
||||
struct GroupParams {
|
||||
int n_runs, n_groups;
|
||||
const int32_t *perm, *rr_start, *rr_count, *rr_group;
|
||||
const int32_t *sorted_run, *group_first; // group g = runs sorted_run[group_first[g] .. [g+1])
|
||||
const float *I, *sigma, *pcorr;
|
||||
const ObsRecord *obs;
|
||||
int32_t *group, *group_start, *group_count, *group_perm;
|
||||
};
|
||||
|
||||
@@ -478,7 +481,7 @@ namespace {
|
||||
const int g = p.rr_group[r];
|
||||
for (int q = p.rr_start[r]; q < p.rr_start[r] + p.rr_count[r]; ++q) {
|
||||
const int i = p.perm[q];
|
||||
p.group[i] = (g >= 0 && ObsFinite(i, p.I, p.sigma, p.pcorr)) ? g : -1;
|
||||
p.group[i] = (g >= 0 && ObsFinite(i, p.obs)) ? g : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,7 +492,7 @@ namespace {
|
||||
for (int j = p.group_first[g]; j < p.group_first[g + 1]; ++j) {
|
||||
const int r = p.sorted_run[j];
|
||||
for (int q = p.rr_start[r]; q < p.rr_start[r] + p.rr_count[r]; ++q)
|
||||
c += ObsFinite(p.perm[q], p.I, p.sigma, p.pcorr) ? 1 : 0;
|
||||
c += ObsFinite(p.perm[q], p.obs) ? 1 : 0;
|
||||
}
|
||||
p.group_count[g] = c;
|
||||
}
|
||||
@@ -506,7 +509,7 @@ namespace {
|
||||
const int r = p.sorted_run[j];
|
||||
for (int q = p.rr_start[r]; q < p.rr_start[r] + p.rr_count[r]; ++q) {
|
||||
const int i = p.perm[q];
|
||||
if (ObsFinite(i, p.I, p.sigma, p.pcorr)) a[n++] = i;
|
||||
if (ObsFinite(i, p.obs)) a[n++] = i;
|
||||
}
|
||||
}
|
||||
auto sift = [a](int root, int end) {
|
||||
@@ -526,6 +529,21 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// One field of a slice of the records, from a contiguous array: rec[i].field = v[i], the field being
|
||||
// the record's float number `field`.
|
||||
__global__ void SetRecordFieldKernel(int n, const float *__restrict__ v, ObsRecord *__restrict__ rec, int field) {
|
||||
float *f = reinterpret_cast<float *>(rec);
|
||||
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += gridDim.x * blockDim.x)
|
||||
f[size_t(i) * 4 + field] = v[i];
|
||||
}
|
||||
|
||||
// The fulls' records for their Unity scaling: partiality and prescaling_corr 1.
|
||||
__global__ void FullsRecordKernel(int n, const float *__restrict__ I, const float *__restrict__ sigma,
|
||||
ObsRecord *__restrict__ obs) {
|
||||
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += gridDim.x * blockDim.x)
|
||||
obs[i] = {I[i], sigma[i], 1.0f, 1.0f};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void FillKernel(T *p, int n, T v) {
|
||||
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += gridDim.x * blockDim.x) p[i] = v;
|
||||
@@ -1088,11 +1106,10 @@ namespace {
|
||||
std::string("RotationScaleMergeGPU: ") + what + ": " + cudaGetErrorString(e));
|
||||
}
|
||||
|
||||
// Device bytes per observation in the arrays SetPartialsLayout allocates: twelve floats (I, sigma,
|
||||
// prescaling_corr, partiality, zeta, corr, bkg, var_bkg, image_number, d, px, py), frame and
|
||||
// sco_coeff (4 bytes each), inv_sigma (8), on_ice, clipped and sco_ok (1 each). The merge's other
|
||||
// buffers come on top of these.
|
||||
constexpr size_t OBS_BYTES = 12 * 4 + 4 + 4 + 8 + 3 * 1;
|
||||
// Device bytes per observation in the arrays SetPartialsLayout allocates: twelve floats (the record's
|
||||
// I, sigma, partiality and prescaling_corr; zeta, corr, bkg, var_bkg, image_number, d, px, py), frame
|
||||
// (4 bytes), on_ice and clipped (1 each). The merge's other buffers come on top of these.
|
||||
constexpr size_t OBS_BYTES = 12 * 4 + 4 + 2 * 1;
|
||||
|
||||
// Every buffer here is sized by the data set, and the scaling does not fall back to the CPU
|
||||
// mid-run: the two paths differ in the last bits, so the result would depend on the card. A
|
||||
@@ -1190,7 +1207,9 @@ struct RotationScaleMergeGPU::Impl {
|
||||
}
|
||||
|
||||
// immutable per-obs
|
||||
CudaDevicePtr<float> I, sigma, prescaling_corr, partiality, zeta;
|
||||
CudaDevicePtr<ObsRecord> obs;
|
||||
void UploadRecordChunk(int field, int offset, int count, const float *v);
|
||||
CudaDevicePtr<float> zeta;
|
||||
CudaDevicePtr<uint8_t> on_ice, clipped;
|
||||
CudaDevicePtr<int32_t> frame;
|
||||
CudaDevicePtr<float> corr; // mutable, resident across iterations
|
||||
@@ -1200,9 +1219,6 @@ struct RotationScaleMergeGPU::Impl {
|
||||
// scratch
|
||||
CudaDevicePtr<double> group_mean, g, info;
|
||||
CudaDevicePtr<uint8_t> scaled;
|
||||
CudaDevicePtr<double> inv_sigma; // 1/sigma, hoisted out of the IRLS loop (sigma never changes)
|
||||
CudaDevicePtr<float> sco_coeff;
|
||||
CudaDevicePtr<uint8_t> sco_ok;
|
||||
CudaDevicePtr<double> cc; // per-frame CC (diagnostic), length n_frames
|
||||
CudaDevicePtr<int64_t> cc_n;
|
||||
CudaDevicePtr<uint8_t> smooth_apply; // per-frame smooth-G apply flag + ratio, length n_frames
|
||||
@@ -1243,12 +1259,12 @@ struct RotationScaleMergeGPU::Impl {
|
||||
CudaDevicePtr<int32_t> f_h, f_k, f_l, f_frame, f_group;
|
||||
CudaDevicePtr<float> f_I, f_sigma, f_d, f_img, f_px, f_py, f_var_bkg, f_var_per_I, f_capture;
|
||||
CudaDevicePtr<uint8_t> f_on_ice, f_clipped;
|
||||
// scale-fulls (Unity model, kept resident): all-ones partiality/prescaling_corr/zeta so the shared scaling kernels
|
||||
// yield coeff=mean, plus the working corr, the per-obs scale scratch, and the fulls frame/group CSRs
|
||||
// (built on the host from the small f_frame/f_group key arrays, over the emit-ordered fulls).
|
||||
CudaDevicePtr<float> f_corr, f_partiality, f_rlp, f_zeta, f_sco_coeff;
|
||||
CudaDevicePtr<double> f_inv_sigma;
|
||||
CudaDevicePtr<uint8_t> f_sco_ok;
|
||||
// scale-fulls (Unity model, kept resident): all-ones partiality/zeta and records with partiality and
|
||||
// prescaling_corr 1, so the shared scaling kernels yield coeff=mean, plus the working corr and the
|
||||
// fulls frame/group CSRs (built on the host from the small f_frame/f_group key arrays, over the
|
||||
// emit-ordered fulls).
|
||||
CudaDevicePtr<float> f_corr, f_partiality, f_zeta;
|
||||
CudaDevicePtr<ObsRecord> f_obs;
|
||||
CudaDevicePtr<int32_t> f_frame_perm, f_frame_start, f_frame_count;
|
||||
CudaDevicePtr<int32_t> f_gperm, f_gstart, f_gcount;
|
||||
// correction-surface fit (one ApplyCellSurface call at a time): its terms and their group CSR, the
|
||||
@@ -1320,8 +1336,7 @@ void RotationScaleMergeGPU::SetPartialsLayout(int n_obs, int n_frames,
|
||||
ThrowTooLargeForGPU(n_obs);
|
||||
d.Upload(d.frame_start, frame_start, n_frames); d.Upload(d.frame_count, frame_count, n_frames);
|
||||
const int n = std::max(1, n_obs);
|
||||
d.I = d.Alloc<float>(n); d.sigma = d.Alloc<float>(n);
|
||||
d.prescaling_corr = d.Alloc<float>(n); d.partiality = d.Alloc<float>(n);
|
||||
d.obs = d.Alloc<ObsRecord>(n);
|
||||
d.zeta = d.Alloc<float>(n); d.corr = d.Alloc<float>(n);
|
||||
d.bkg = d.Alloc<float>(n); d.var_bkg = d.Alloc<float>(n);
|
||||
d.image_number = d.Alloc<float>(n); d.d_obs = d.Alloc<float>(n);
|
||||
@@ -1332,9 +1347,6 @@ void RotationScaleMergeGPU::SetPartialsLayout(int n_obs, int n_frames,
|
||||
d.g = d.Alloc<double>(n_frames);
|
||||
d.scaled = d.Alloc<uint8_t>(n_frames);
|
||||
d.info = d.Alloc<double>(n_frames);
|
||||
d.inv_sigma = d.Alloc<double>(n_obs);
|
||||
d.sco_coeff = d.Alloc<float>(n_obs);
|
||||
d.sco_ok = d.Alloc<uint8_t>(n_obs);
|
||||
d.cc = d.Alloc<double>(std::max(1, n_frames));
|
||||
d.cc_n = d.Alloc<int64_t>(std::max(1, n_frames));
|
||||
}
|
||||
@@ -1346,16 +1358,30 @@ namespace {
|
||||
CopyAndWait(dst.get() + offset, v, size_t(count) * sizeof(T), cudaMemcpyHostToDevice, s,
|
||||
"upload chunk");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// A slice of one field of the records (`field` its float number), staged contiguously and then put
|
||||
// in place on the device: a strided copy from pageable memory goes element by element.
|
||||
void RotationScaleMergeGPU::Impl::UploadRecordChunk(int field, int offset, int count, const float *v) {
|
||||
if (count == 0)
|
||||
return;
|
||||
auto staged = Alloc<float>(count);
|
||||
CopyAndWait(staged.get(), v, size_t(count) * sizeof(float), cudaMemcpyHostToDevice, s(), "upload record chunk");
|
||||
SetRecordFieldKernel<<<std::min(65535, (count + BLK - 1) / BLK), BLK, 0, s()>>>(count, staged.get(),
|
||||
obs.get() + offset, field);
|
||||
CudaCheck(cudaGetLastError(), "record field launch");
|
||||
CudaCheck(cudaStreamSynchronize(s()), "upload record chunk");
|
||||
}
|
||||
|
||||
void RotationScaleMergeGPU::SetObsField(ObsField f, int offset, int count, const float *v) {
|
||||
DeviceGuard guard(impl_->device, impl_->available);
|
||||
auto &d = *impl_;
|
||||
switch (f) {
|
||||
case ObsField::I: UploadChunk(d.I, offset, count, v, impl_->s()); break;
|
||||
case ObsField::Sigma: UploadChunk(d.sigma, offset, count, v, impl_->s()); break;
|
||||
case ObsField::PrescalingCorr: UploadChunk(d.prescaling_corr, offset, count, v, impl_->s()); break;
|
||||
case ObsField::Partiality: UploadChunk(d.partiality, offset, count, v, impl_->s()); break;
|
||||
case ObsField::I: d.UploadRecordChunk(0, offset, count, v); break;
|
||||
case ObsField::Sigma: d.UploadRecordChunk(1, offset, count, v); break;
|
||||
case ObsField::Partiality: d.UploadRecordChunk(2, offset, count, v); break;
|
||||
case ObsField::PrescalingCorr: d.UploadRecordChunk(3, offset, count, v); break;
|
||||
case ObsField::Zeta: UploadChunk(d.zeta, offset, count, v, impl_->s()); break;
|
||||
case ObsField::Corr0: UploadChunk(d.corr, offset, count, v, impl_->s()); break;
|
||||
case ObsField::Bkg: UploadChunk(d.bkg, offset, count, v, impl_->s()); break;
|
||||
@@ -1375,8 +1401,7 @@ void RotationScaleMergeGPU::CopyObservationsFrom(const RotationScaleMergeGPU &fr
|
||||
CudaCheck(cudaMemcpyPeerAsync(dst.get(), d.device, src.get(), f.device,
|
||||
size_t(d.n_obs) * sizeof(*src.get()), d.s()), "copy observations");
|
||||
};
|
||||
copy(d.I, f.I); copy(d.sigma, f.sigma); copy(d.prescaling_corr, f.prescaling_corr);
|
||||
copy(d.partiality, f.partiality); copy(d.zeta, f.zeta); copy(d.corr, f.corr);
|
||||
copy(d.obs, f.obs); copy(d.zeta, f.zeta); copy(d.corr, f.corr);
|
||||
copy(d.bkg, f.bkg); copy(d.var_bkg, f.var_bkg); copy(d.image_number, f.image_number);
|
||||
copy(d.d_obs, f.d_obs); copy(d.px_obs, f.px_obs); copy(d.py_obs, f.py_obs);
|
||||
copy(d.frame, f.frame); copy(d.on_ice, f.on_ice); copy(d.clipped, f.clipped);
|
||||
@@ -1416,7 +1441,7 @@ void RotationScaleMergeGPU::BuildGroups(int n_groups, const int32_t *rawrun_grou
|
||||
p.n_runs = d.n_runs; p.n_groups = n_groups;
|
||||
p.perm = d.perm.get(); p.rr_start = d.rr_start.get(); p.rr_count = d.rr_count.get(); p.rr_group = d.rr_group.get();
|
||||
p.sorted_run = sorted.get(); p.group_first = first.get();
|
||||
p.I = d.I.get(); p.sigma = d.sigma.get(); p.pcorr = d.prescaling_corr.get();
|
||||
p.obs = d.obs.get();
|
||||
p.group = d.group.get(); p.group_start = d.group_start.get(); p.group_count = d.group_count.get();
|
||||
const int run_blocks = std::min(65535, (d.n_runs + BLK - 1) / BLK);
|
||||
const int grp_blocks = std::min(65535, (n_groups + BLK - 1) / BLK);
|
||||
@@ -1456,23 +1481,18 @@ void RotationScaleMergeGPU::ScalePartials(int iters, double min_partiality, bool
|
||||
// call did not fit must read as unfitted, not as fitted with the value of the call before.
|
||||
CudaCheck(cudaMemsetAsync(d.scaled.get(), 0, size_t(d.n_frames) * sizeof(uint8_t), impl_->s()), "memset scaled");
|
||||
CudaCheck(cudaMemsetAsync(d.g.get(), 0, size_t(d.n_frames) * sizeof(double), impl_->s()), "memset g"); // unscaled g unused
|
||||
const int obs_blocks = (d.n_obs + BLK - 1) / BLK;
|
||||
const int upd_blocks = std::min(65535, obs_blocks);
|
||||
const int upd_blocks = std::min(65535, (d.n_obs + BLK - 1) / BLK);
|
||||
const int grp_blocks = std::min(65535, (d.n_groups + BLK - 1) / BLK);
|
||||
for (int it = 0; it < iters; ++it) {
|
||||
ReduceGroupMeansKernel<<<grp_blocks, BLK, 0, impl_->s()>>>(d.n_groups, min_partiality,
|
||||
d.group_perm.get(), d.group_start.get(), d.group_count.get(),
|
||||
d.I.get(), d.sigma.get(), d.partiality.get(), d.corr.get(), d.group_mean.get());
|
||||
d.obs.get(), d.corr.get(), d.group_mean.get());
|
||||
CudaCheck(cudaGetLastError(), "ReduceGroupMeansKernel launch");
|
||||
PrepScaleObsKernel<<<obs_blocks, BLK, 0, impl_->s()>>>(d.n_obs, min_partiality, d.group.get(), d.partiality.get(), d.prescaling_corr.get(),
|
||||
d.zeta.get(), d.on_ice.get(), d.group_mean.get(), d.sigma.get(), d.inv_sigma.get(),
|
||||
d.sco_coeff.get(), d.sco_ok.get());
|
||||
CudaCheck(cudaGetLastError(), "PrepScaleObsKernel launch");
|
||||
FitPerFrameGKernel<<<d.n_frames, BLK, 0, impl_->s()>>>(d.n_frames, d.frame_start.get(), d.frame_count.get(),
|
||||
d.I.get(), d.inv_sigma.get(), d.sco_coeff.get(), d.sco_ok.get(), nullptr, long(MIN_REFLECTIONS), d.g.get(), d.scaled.get(),
|
||||
d.info.get());
|
||||
nullptr, min_partiality, d.group.get(), d.obs.get(), d.zeta.get(), d.on_ice.get(), d.group_mean.get(),
|
||||
long(MIN_REFLECTIONS), d.g.get(), d.scaled.get(), d.info.get());
|
||||
CudaCheck(cudaGetLastError(), "FitPerFrameGKernel launch");
|
||||
UpdateCorrKernel<<<upd_blocks, BLK, 0, impl_->s()>>>(d.n_obs, d.frame.get(), d.prescaling_corr.get(), d.partiality.get(),
|
||||
UpdateCorrKernel<<<upd_blocks, BLK, 0, impl_->s()>>>(d.n_obs, d.frame.get(), d.obs.get(),
|
||||
d.g.get(), d.scaled.get(), d.corr.get());
|
||||
}
|
||||
CudaCheck(cudaGetLastError(), "kernel launch");
|
||||
@@ -1838,11 +1858,11 @@ void RotationScaleMergeGPU::ComputePartialCC(double min_partiality, double *cc_o
|
||||
// per-frame CC over the resident partials. Only the tiny per-frame cc/cc_n come back to the host.
|
||||
ReduceGroupMeansKernel<<<grp_blocks, BLK, 0, impl_->s()>>>(d.n_groups, min_partiality,
|
||||
d.group_perm.get(), d.group_start.get(), d.group_count.get(),
|
||||
d.I.get(), d.sigma.get(), d.partiality.get(), d.corr.get(), d.group_mean.get());
|
||||
d.obs.get(), d.corr.get(), d.group_mean.get());
|
||||
CudaCheck(cudaGetLastError(), "ReduceGroupMeansKernel launch");
|
||||
PerFrameCCKernel<<<d.n_frames, BLK, 0, impl_->s()>>>(d.n_frames, min_partiality,
|
||||
d.frame_start.get(), d.frame_count.get(), d.I.get(), d.sigma.get(), d.partiality.get(),
|
||||
d.corr.get(), d.on_ice.get(), d.group.get(), d.group_mean.get(), d.cc.get(), d.cc_n.get());
|
||||
d.frame_start.get(), d.frame_count.get(), d.obs.get(), d.corr.get(), d.on_ice.get(), d.group.get(),
|
||||
d.group_mean.get(), d.cc.get(), d.cc_n.get());
|
||||
CudaCheck(cudaGetLastError(), "partial CC launch");
|
||||
CudaCheck(cudaStreamSynchronize(impl_->s()), "partial CC sync");
|
||||
CopyAndWait(cc_out, d.cc.get(), size_t(d.n_frames) * sizeof(double),
|
||||
@@ -1885,7 +1905,7 @@ int RotationScaleMergeGPU::Combine(const int32_t *rawrun_group, double min_parti
|
||||
p.capture_uncertainty_coeff = capture_uncertainty_coeff;
|
||||
p.min_captured_fraction = min_captured_fraction;
|
||||
p.max_frame_gap = max_frame_gap;
|
||||
p.I = d.I.get(); p.sigma = d.sigma.get(); p.corr = d.corr.get(); p.partiality = d.partiality.get();
|
||||
p.obs = d.obs.get(); p.corr = d.corr.get();
|
||||
p.bkg = d.bkg.get(); p.var_bkg = d.var_bkg.get(); p.image_number = d.image_number.get(); p.d = d.d_obs.get();
|
||||
p.px = d.px_obs.get(); p.py = d.py_obs.get();
|
||||
p.frame = d.frame.get(); p.on_ice = d.on_ice.get(); p.clipped = d.clipped.get();
|
||||
@@ -1924,9 +1944,8 @@ int RotationScaleMergeGPU::Combine(const int32_t *rawrun_group, double min_parti
|
||||
d.f_var_bkg = d.Alloc<float>(nf); d.f_var_per_I = d.Alloc<float>(nf); d.f_capture = d.Alloc<float>(nf);
|
||||
d.f_on_ice = d.Alloc<uint8_t>(nf); d.f_clipped = d.Alloc<uint8_t>(nf);
|
||||
d.f_corr = d.Alloc<float>(nf); d.f_partiality = d.Alloc<float>(nf);
|
||||
d.f_rlp = d.Alloc<float>(nf); d.f_zeta = d.Alloc<float>(nf);
|
||||
d.f_inv_sigma = d.Alloc<double>(nf);
|
||||
d.f_sco_coeff = d.Alloc<float>(nf); d.f_sco_ok = d.Alloc<uint8_t>(nf);
|
||||
d.f_zeta = d.Alloc<float>(nf);
|
||||
d.f_obs = d.Alloc<ObsRecord>(nf);
|
||||
CopyAndWait(d.rr_offset.get(), offset.data(), size_t(d.n_runs) * sizeof(int32_t),
|
||||
cudaMemcpyHostToDevice, impl_->s(), "upload offset");
|
||||
|
||||
@@ -2002,8 +2021,8 @@ void RotationScaleMergeGPU::ResetFullsScale() {
|
||||
CudaCheck(cudaGetLastError(), "FillKernel launch");
|
||||
FillKernel<<<obs_blocks, BLK, 0, impl_->s()>>>(d.f_partiality.get(), nf, 1.0f);
|
||||
CudaCheck(cudaGetLastError(), "FillKernel launch");
|
||||
FillKernel<<<obs_blocks, BLK, 0, impl_->s()>>>(d.f_rlp.get(), nf, 1.0f);
|
||||
CudaCheck(cudaGetLastError(), "FillKernel launch");
|
||||
FullsRecordKernel<<<obs_blocks, BLK, 0, impl_->s()>>>(nf, d.f_I.get(), d.f_sigma.get(), d.f_obs.get());
|
||||
CudaCheck(cudaGetLastError(), "FullsRecordKernel launch");
|
||||
FillKernel<<<obs_blocks, BLK, 0, impl_->s()>>>(d.f_zeta.get(), nf, 1.0f);
|
||||
CudaCheck(cudaGetLastError(), "FillKernel launch");
|
||||
CudaCheck(cudaStreamSynchronize(impl_->s()), "reset fulls scale sync");
|
||||
@@ -2022,19 +2041,11 @@ void RotationScaleMergeGPU::FitFullsScale(double min_partiality) {
|
||||
|
||||
ReduceGroupMeansKernel<<<grp_blocks, BLK, 0, impl_->s()>>>(d.n_groups, min_partiality,
|
||||
d.f_gperm.get(), d.f_gstart.get(), d.f_gcount.get(),
|
||||
d.f_I.get(), d.f_sigma.get(), d.f_partiality.get(), d.f_corr.get(), d.group_mean.get());
|
||||
d.f_obs.get(), d.f_corr.get(), d.group_mean.get());
|
||||
CudaCheck(cudaGetLastError(), "ReduceGroupMeansKernel launch");
|
||||
// Not grid-stride, so its grid has to cover every full - unlike the grid-stride kernels
|
||||
// below, which the 65535 cap is there for. Capped, it would silently leave the tail of
|
||||
// sco_coeff/sco_ok stale above 16.8M fulls.
|
||||
PrepScaleObsKernel<<<(nf + BLK - 1) / BLK, BLK, 0, impl_->s()>>>(nf, min_partiality, d.f_group.get(), d.f_partiality.get(),
|
||||
d.f_rlp.get(), d.f_zeta.get(), d.f_on_ice.get(), d.group_mean.get(),
|
||||
d.f_sigma.get(), d.f_inv_sigma.get(), d.f_sco_coeff.get(), d.f_sco_ok.get());
|
||||
CudaCheck(cudaGetLastError(), "PrepScaleObsKernel launch");
|
||||
FitPerFrameGKernel<<<d.n_frames, BLK, 0, impl_->s()>>>(d.n_frames,
|
||||
d.f_frame_start.get(), d.f_frame_count.get(), d.f_I.get(), d.f_inv_sigma.get(),
|
||||
d.f_sco_coeff.get(), d.f_sco_ok.get(), d.f_frame_perm.get(), 1L, d.g.get(), d.scaled.get(),
|
||||
d.info.get());
|
||||
FitPerFrameGKernel<<<d.n_frames, BLK, 0, impl_->s()>>>(d.n_frames, d.f_frame_start.get(), d.f_frame_count.get(),
|
||||
d.f_frame_perm.get(), min_partiality, d.f_group.get(), d.f_obs.get(), d.f_zeta.get(), d.f_on_ice.get(),
|
||||
d.group_mean.get(), 1L, d.g.get(), d.scaled.get(), d.info.get());
|
||||
CudaCheck(cudaGetLastError(), "scale fulls launch");
|
||||
CudaCheck(cudaStreamSynchronize(impl_->s()), "scale fulls sync");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user