Skip to content

Fix crash when there is a gap in input data - #1077

Open
bmerry wants to merge 8 commits into
mainfrom
NGC-2074-data-gap
Open

Fix crash when there is a gap in input data#1077
bmerry wants to merge 8 commits into
mainfrom
NGC-2074-data-gap

Conversation

@bmerry

@bmerry bmerry commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

When there is a gap between input chunks, the output chunk is fast-forwarded to avoid referencing missing data. This is done with batch granularity, so as not to lose more output data than necessary. However, that means that the OutQueueItem is not necessarily aligned (in time) to a multiple of the output chunk size. In our unit tests it generally has been, because there is no delay model and so it ends up aligned to the input chunk size; but when there is a non-zero coarse delay, that's no longer the case. Meanwhile, the code for handling the dig-rms-dbfs sensor works with "windows" which are some multiple of the chunk size and aligned to the window size, and does not cope with OutQueueItems that cross a window boundary. In theory this could also be an issue if a non-zero delay was set before the first data arrived.

Fix it by changing the computation of total digitiser power from being done per-output-chunk (on the GPU) to per-output-batch. I initially experimented with doing it per-output-spectrum, but that was too expensive; even with per-output-batch it needed some experimentation to get the CUDA compiler to generate reasonable code that didn't slow things down massively. I've tested with benchmark_fgpu and found no statistically significant impact on performance:

./benchmark_fgpu.py -v --narrowband=1 --image harbor.cbf.mkat.karoo.kat.ac.za/dpp/katgpucbf:latest
1873.0 MHz - 1893.0 MHz

./benchmark_fgpu.py -v --narrowband=1 --image harbor.cbf.mkat.karoo.kat.ac.za/dpp/katgpucbf:NGC-2074-data-gap
1868.0 MHz - 1888.0 MHz

./benchmark_fgpu.py -v --image harbor.cbf.mkat.karoo.kat.ac.za/dpp/katgpucbf:latest
1955.0 MHz - 1975.0 MHz

./benchmark_fgpu.py -v --image harbor.cbf.mkat.karoo.kat.ac.za/dpp/katgpucbf:NGC-2074-data-gap
1957.0 MHz - 1977.0 MHz

Add a non-zero delay to the existing unit tests for missing data. That made it a lot more complicated unfortunately.

Checklist (if not applicable, edit to add (N/A) and mark as done):

  • (n/a) If dependencies are added/removed: update pyproject.toml and .pre-commit-config.yaml.
  • (n/a) If new source files are added: add copyright notices dated with the current year.
  • (n/a) If qualification tests are changed: attach or link to a sample qualification report.
  • If design has changed: ensure documentation is up to date.
  • (n/a) If ICD-defined sensors have been added: update fake_servers.py in katsdpcontroller to match.
  • (n/a) If the interface between the product controller and an engine has changed, update the
    VERSION constant for the engine (update the major number if sensors/requests have been
    removed/changed, minor number if only backwards-compatible changes have been done).
    Also, update doc/control.rst with the new version.

Closes NGC-2074.

bmerry added 8 commits July 28, 2026 12:28
At present the per-spectrum values are just added up on the host to give
a per-chunk value as before, but this paves the way to fixing NGC-2074.
The GPU now accumulates data up to batch granularity rather than just
spectrum granularity (which was too expensive). This still doesn't have
any benefit as is, but is on the path to fixing NGC-2074.
Instead of testing every iteration whether it is time to emit accumulated results,
use nested loops with the emission in the outer loop.
Now that power stats are collected per-batch instead of per-chunk, we no
longer require the window size to be a multiple of the chunk size (just
of the batch size).
This brings it into line with the other arrays, which are
polarisation-major. It also removes the need to transpose it later on
the CPU.
@bmerry

bmerry commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

I'm requesting @vincentseotlo as primary reviewer. @EatonEmmerich is added more for interest / learning.

int offset = group_y * stepy + pos; // This thread probably doesn't work on the very beginning of the data, so we
// make the indexing easier for ourselves later.
// pos is the position within the step (i.e. spectrum) that this thread will work on.
int pos = get_group_id(0) * WGS + lid;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep the standard of using group_x, so we're in line with group_y. Granted group_y is shifted by stepy.

Comment thread src/katgpucbf/fgpu/pfb.py
# ensures that tests that it divides into other values are
# satisfied.
total_power_spectra = 1
self.total_power_spectra = total_power_spectra

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any danger in just saying self.total_power_spectra = 1, then line 138: "total_power_spectra": self.total_power_spectra,

else:
sensor.set_value(
np.finfo(np.float64).min, status=aiokatcp.Sensor.Status.FAILURE, timestamp=update_timestamp
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do we expect to happen if we accumulate with power=None on an existing value in the accumulator?

Comment thread test/fgpu/test_engine.py
Comment on lines 100 to +103
@pytest.fixture
def dig_rms_dbfs_window_chunks() -> int:
def dig_rms_dbfs_window_batches() -> int:
"""Number of chunks per window for ``dig-rms-dbfs`` sensors."""
return 2
return 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just use a constant?

// Load the raw data for the sample
sample_t sample = unpack_read(&unpack);
unpack_advance(&unpack, step);
{ // Block just to balance things with the !complex_input case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit(non-blocking): missed the ! here first time I read the comment and got confused.
Suggest writing it as not complex_input case.


if dig_total_power is not None:
self._update_dig_power_sensors(dig_total_power_windows, dig_total_power, out_item)
self._update_dig_power_sensors(dig_total_power_windows, dig_total_power, out_item, chunk.present)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we pass the batch_samples in here instead then calculate it from the spectra_per_heap like we do in _dig_rms_dbfs_window_samples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants