Lab051: accelerate 500 ksymbol/s streaming receiver
This commit is contained in:
281
docs/lab051_current_state.md
Normal file
281
docs/lab051_current_state.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Lab051 — вычислительная архитектура потокового BPSK-приёмника
|
||||
|
||||
Дата: 8 сентября 2026 года. SOFTWARE ONLY. RF TX = 0.
|
||||
|
||||
## Результат и граница вывода
|
||||
|
||||
**Программный эксперимент завершён; цели реального времени и свежести не достигнуты.**
|
||||
В полном шестисекундном потоке получены 240/240 физических пакетов и 30/30 JPEG
|
||||
с точным содержимым. Обработка 6,044416 с синтетического IQ заняла **38,630132 с**,
|
||||
отношение **6,391045**. При MAX_VIDEO_AGE=200 мс **FRESH DISPLAY FPS=0**;
|
||||
не выполнен даже минимальный критерий >0.
|
||||
|
||||
Короткий шумный сценарий Lab050 ускорен примерно в **48,21 раза** относительно
|
||||
исторического замера: 55,157553 с → медиана **1,144176 с** трёх проходов.
|
||||
Его processing/RF = **3,534193**, поэтому и этот вариант не работает в реальном времени.
|
||||
Среднее успешного кандидата — **17,7675 мс**; полная стоимость короткого опыта —
|
||||
**47,674 мс/пакет**, а длинного потока — **160,959 мс/пакет**.
|
||||
Последние величины включают обработку ложных кандидатов и фона.
|
||||
|
||||
Модуляция BPSK, PRBS7, PHY-заголовок, фрагментация, интерливер, FEC R=1/2 K=7,
|
||||
171/133 octal, soft-метрика и CRC не менялись. GPU и multiprocessing не использовались.
|
||||
Новых зависимостей нет. Аппаратура не перечислялась и не запускалась.
|
||||
|
||||
## Git и аудит до реализации
|
||||
|
||||
Исходный main: `cdf5396e807b83f82a264632dc46f71428fcd616`.
|
||||
Проверены HEAD/status/diff stat/diff check, затем показан точный staged scope.
|
||||
По прямому разрешению пункта 0 задания создан коммит:
|
||||
|
||||
`60691a09f193f36983c7174c5252ed0897c559e9`
|
||||
|
||||
`Lab050: model fresh-frame SDR video stream`
|
||||
|
||||
В него вошли ровно девять файлов: PROJECT_LOG.md, docs/LABS_INDEX.md,
|
||||
experiments/lab046c_offline_recovery.py, protocol/video_frame_scheduler.py,
|
||||
docs/lab050_current_state.md, experiments/lab050_streaming_receiver.py,
|
||||
experiments/lab050_video_stream.py, tests/test_lab050_video_stream.py,
|
||||
data/processed/lab050/receiver_benchmarks.json. 1769 добавлений, 12 удалений.
|
||||
Большой result.json, профили JPEG и посторонние локальные файлы не добавлялись.
|
||||
|
||||
Lab051 начата от этого HEAD. Изучены Lab046B/C, Lab047, Lab048, Lab050,
|
||||
Lab043/044/045 и свёрточный декодер. Ядро protocol в Lab051 не менялось.
|
||||
Планировщик LATEST_ONLY и ObjectAssembler переиспользованы. Новая видеочередь не создана.
|
||||
|
||||
## Профиль до оптимизации
|
||||
|
||||
Рабочая точка: 500000 символов/с, Fs=8000000 отсчётов/с, 16 отсчётов/символ.
|
||||
Профиль C: 128×96 RGB, JPEG quality 25. Короткий контроль: три изображения,
|
||||
24 пакета, seed=50050, coded-bit Eb/N0=7 дБ, CFO=+10 Гц, фаза 0,73 рад,
|
||||
чередование полярности, исходное CS8-квантование. Генератор точно прежний, из Lab050.
|
||||
|
||||
Путь кандидата: IQ → грубая нормированная корреляция → уточнение старта →
|
||||
согласованный RRC-фильтр → timing → оценка частотного рассогласования CFO →
|
||||
компенсация CFO и фазы → мягкие решения → PHY header → deinterleave →
|
||||
soft Viterbi → protocol parser → CRC. Только полный PHY/FEC/CRC PASS выдаёт пакет.
|
||||
TX-границы, ожидаемое число кадров и содержимое не входят в RX API.
|
||||
|
||||
Историческая база Lab050: 55,157553 / 0,323745 = 170,373647.
|
||||
Свежий инструментированный baseline Lab051: **49,614327 с**, processing/RF=153,251430.
|
||||
Настольная нагрузка и инструментация влияют на время; эти замеры не подменяют друг друга.
|
||||
Все 2374 кандидата: 24 valid, 2350 false. Средние времена — **72,967 / 20,099 мс**.
|
||||
|
||||
**Главная причина замедления — fallback timing.** Номинальное положение равно 416,
|
||||
а диапазон остался −800…+800 отсчётов. Начало выходит за ноль, и прежний быстрый
|
||||
путь возвращается к Python-перебору 1601 смещения. У ложных кандидатов timing
|
||||
занял 41,833 с из 47,232 с строгой обработки. Их matched filter — 0,934 с,
|
||||
уточнение — 2,797 с. Прежняя реализация уже останавливала кандидата при PHY FAIL:
|
||||
тело, Viterbi и CRC на таких входах не запускались.
|
||||
|
||||
Для преамбулы и заголовка достаточно 23456 входных отсчётов и 23616 отсчётов
|
||||
после RRC. Полный физический пакет с payload 256 байт требует 92256 входных
|
||||
отсчётов. Длина тела определяется только проверенным PHY-заголовком.
|
||||
|
||||
## Изменения вычислений
|
||||
|
||||
1. Децимация сохраняет внутреннюю часть конечного импульсного отклика FIR.
|
||||
При сдвиге буфера заново считаются левый нулевой край и новые правые отсчёты.
|
||||
Коэффициенты resample_poly/kaiser(8) прежние; сравнение с исходной функцией
|
||||
побитно точное. Среднее по всему прежнему окну сохраняется. Корреляция считается
|
||||
только для принадлежащих блоку стартов плюс правый сосед максимума.
|
||||
2. Корреляция известного prefix для уточнения считается один раз для общего участка.
|
||||
Кандидаты используют срезы, с прежней поправкой локального DC.
|
||||
Постоянный probe и неразделимые FFT-пики сохраняют исходный выбор первого
|
||||
максимума; при необходимости вызывается старое уточнение.
|
||||
3. Компенсация известного +50 кГц и RRC выполняются для общего участка.
|
||||
Локальное вычитание среднего и оба нулевых края изолированного окна
|
||||
восстановлены отдельно. Без этих поправок общая фильтрация меняла бы алгоритм.
|
||||
Тело за пределами общего участка фильтруется локально только после PHY PASS.
|
||||
4. Извлечение символов кэшируется по уточнённому положению и длине внутри одного блока.
|
||||
Усечённое окно не подменяется ранее сохранённым полным окном.
|
||||
Кэш уничтожается вместе с блоком; история сессии не сохраняется.
|
||||
5. Timing сохраняет весь прежний диапазон 1601 смещения. Корреляция с разреженной
|
||||
известной преамбулой и разности накопленных энергий заменяют Python-перебор.
|
||||
Тело за последним нужным отсчётом не участвует в этом расчёте. При пиках,
|
||||
неразделимых с точностью float32, возвращается исходный расчёт и его tie-правило.
|
||||
6. CFO по прежним преамбульным символам не изменён. Векторы переноса кэшируются;
|
||||
вектор остаточной частоты строится на символах локального окна. Gain/фаза для
|
||||
probe и полного кадра не объединены: у них различается локальное среднее IQ.
|
||||
7. Viterbi после устранения главного повтора остался существенной долей принятого
|
||||
пакета. Предвычислены четыре различные пары евклидовых branch metrics;
|
||||
две ветви сравниваются непосредственно, с прежним выбором branch 0 при равенстве.
|
||||
Решётка, старт/финиш 0, шесть хвостовых битов и декодированные биты сохранены.
|
||||
|
||||
Дополнительный порог преамбулы/новый критерий отсева не введён. Фиксированный
|
||||
порог первого каскада **0.030964473262429237** сохранён. В коротком опыте совпали
|
||||
число кандидатов и все причины отказа с исходным RX. Параллелизм и CUDA не добавлялись.
|
||||
|
||||
## Профиль стадий
|
||||
|
||||
Ячейка: **число вызовов / суммарные миллисекунды** по соответствующей группе.
|
||||
Это не длительность одного вызова. Общая работа front-end учитывается в том
|
||||
кандидате, который впервые запросил её. Фаза показана без вложенного CFO;
|
||||
время body pipeline не складывается повторно со временем Viterbi/CRC.
|
||||
|
||||
| Стадия | Baseline valid (24) | Optimized valid (24) | Baseline false (2350) | Optimized false (2350) |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Точное уточнение | 24 / 28.974 | 24 / 93.878 | 2350 / 2797.404 | 2350 / 119.743 |
|
||||
| Согласованная RRC-фильтрация | 48 / 190.656 | 48 / 102.875 | 2336 / 933.965 | 174 / 26.502 |
|
||||
| Timing: весь диапазон | 48 / 914.632 | 48 / 43.535 | 2336 / 41833.166 | 174 / 138.588 |
|
||||
| CFO и комплексное усиление | 48 / 10.838 | 48 / 8.442 | 2336 / 485.772 | 174 / 26.385 |
|
||||
| Фаза/полярность и диагностика | 48 / 8.994 | 48 / 8.324 | 2336 / 424.756 | 174 / 28.438 |
|
||||
| PHY header | 48 / 1.711 | 48 / 1.452 | 2298 / 67.136 | 2298 / 32.044 |
|
||||
| Viterbi | 24 / 484.381 | 24 / 132.119 | 0 / 0 | 0 / 0 |
|
||||
| Protocol parser / CRC | 24 / 0.270 | 24 / 0.211 | 0 / 0 | 0 / 0 |
|
||||
|
||||
Грубая локализация — общая стадия: 36 вызовов, baseline 498,040 мс,
|
||||
optimized **89,183 мс**. На один false candidate среднее **0,187317 мс**,
|
||||
медиана **0,060500 мс**; это включает cache hits. Холодное новое окно дороже.
|
||||
|
||||
## Массивы и память
|
||||
|
||||
Число всех аллокаций внутри NumPy/SciPy C не измерено. Line tracer наблюдает
|
||||
массивы в локальных переменных проектного Python: число ниже — **нижняя граница**,
|
||||
включающая views. Владельцы данных выделены отдельно. Временные выражения и
|
||||
внутренние C-массивы не перечислены. Отдельный tracemalloc-проход измеряет пик
|
||||
отслеживаемой памяти. Эти медленные проходы не включены в CPU benchmark.
|
||||
|
||||
| Кандидат / алгоритм | Наблюдаемые ndarray | Из них владельцы | Суммарные байты наблюдаемых владельцев | Пик tracemalloc, байт |
|
||||
|---|---:|---:|---:|---:|
|
||||
| false / baseline | 2888 | 61 | 6612268 | 2104129 |
|
||||
| false / optimized | 76 | 61 | 5906101 | 5524392 |
|
||||
| valid / baseline | 14361 | 8704 | 16681890 | 5885299 |
|
||||
| valid / optimized | 6594 | 6560 | 12345244 | 7771675 |
|
||||
|
||||
Пик холодного кандидата вырос из-за общих промежуточных данных: ускорение не
|
||||
объявляется уменьшением каждого временного массива. В рабочем потоке:
|
||||
|
||||
- Пик удерживаемых IQ: **3177728 байт = 3,031 МиБ**.
|
||||
- Проверяемый предел IQ: 3398912 байт; кэш децимации хранится отдельно.
|
||||
- Инженерная оценка рабочих массивов: **136,161 МиБ**. Использован запас прежних
|
||||
22 размеров IQ-окна плюс 20 размеров на новые массивы и кэши. Это оценка для
|
||||
данного опыта, не доказанный универсальный предел всех аллокаций и не RSS.
|
||||
- Windows process RSS после потока: **154,105 МиБ**; peak RSS процесса —
|
||||
**178,004 МиБ**. В него входят Python, библиотеки, генератор, предыдущие замеры и аллокатор.
|
||||
- Текущий объект ограничен 65536 байтами / 269 фрагментами; C фактически ≤1893 байт.
|
||||
|
||||
Длинный тест: **4096 блоков, 33,554432 с модельного времени**. Фон нулевой;
|
||||
шум отдельно проверен в основном опыте. После прогрева удерживаются 2653440 байт
|
||||
IQ и 397216 байт децимированных данных; значения не растут.
|
||||
RSS контрольных точек: **156,199–156,250 МиБ**, без роста вместе с длительностью.
|
||||
После finish эти буферы освобождены. История JSON относится к отчёту, не к RX.
|
||||
|
||||
## Непрерывный поток 5 изображений/с
|
||||
|
||||
30 воспроизводимых сдвигов исходного data/raw/lab011_scene.jpg, профиль C,
|
||||
1740–1893 байта. Это искусственная последовательность, не видео камеры.
|
||||
Видеопланировщик LATEST_ONLY использует время capture и завершает активное JPEG.
|
||||
Передатчик знает нижнюю задержку радио/буфера; он не получает будущую стоимость
|
||||
CPU или результаты приёма. Приёмник получает лишь IQ-блоки по 65536 отсчётов.
|
||||
|
||||
Шум, фаза, CFO и полярность заданы, но готовность JPEG берётся из измеренного encode.
|
||||
Поэтому точное размещение пакетов и число фоновых кандидатов длинного опыта могут
|
||||
немного меняться между запусками. Короткий baseline использует точно прежний
|
||||
детерминированный сценарий Lab050.
|
||||
|
||||
Время CPU-работника для каждого блока:
|
||||
`start=max(previous_end, block_arrival); end=start+measured_push_time`.
|
||||
Сборка/JPEG decode добавляют своё измеренное время. Генерация TX/IQ, SHA-сравнение
|
||||
с передатчиком и запись JSON исключены. Длительность RX не заменена нулём.
|
||||
Все данные реально прошли независимый PHY/FEC/CRC и JPEG decode.
|
||||
|
||||
| Метрика | Итог |
|
||||
|---|---:|
|
||||
| INPUT FPS / FRAMES | 5 / 30 |
|
||||
| TX IMAGES / PHYSICAL PACKETS | 30 / 240 |
|
||||
| DROPPED BEFORE TX | 0 |
|
||||
| MAX WAITING VIDEO FRAMES | 1 |
|
||||
| RX PHYSICAL PACKETS / IMAGES | 240 / 30 |
|
||||
| JPEG SHA MATCH | 30 / 30 |
|
||||
| DROPPED STALE AT DISPLAY | 30 |
|
||||
| DISPLAYED / FRESH DISPLAYED | 0 / 0 |
|
||||
| RAW RECEIVED FPS в первых 6 с CPU-шкалы | 0.833333 |
|
||||
| RAW DISPLAY FPS / FRESH DISPLAY FPS | 0 / 0 |
|
||||
| Возраст декодированных JPEG: min / median / max | 276.515 / 15691.203 / 31150.009 мс |
|
||||
| MAX_VIDEO_AGE | 200 мс |
|
||||
| Строгих кандидатов | 147082 |
|
||||
| Полное время обработки / IQ | 38.630132 / 6.044416 с |
|
||||
| PROCESSING / RF TIME | 6.391045 |
|
||||
|
||||
Возраст относится к **декодированным, затем отброшенным JPEG**. Показанных изображений нет.
|
||||
RAW DISPLAY и FRESH DISPLAY совпадают, потому что политика не выводит просроченные
|
||||
изображения; RAW RECEIVED приведён отдельно.
|
||||
|
||||
Физическое время одного JPEG C: 8×5766/500000 = **92,256 мс**. Очередь ожидающих
|
||||
JPEG в этом режиме не задерживает готовые изображения. Время RX за опыт:
|
||||
38,619085 с; сборка 5,451 мс; JPEG decode 5,596 мс суммарно на 30 JPEG.
|
||||
RF и CPU перекрываются по событийной шкале: эти суммы нельзя просто сложить
|
||||
и назвать измеренной задержкой камеры→экрана. Camera capture latency и display
|
||||
latency **UNKNOWN**; экран в отчётной модели мгновенный.
|
||||
|
||||
Capture lookup используется только **после** независимой сборки JPEG для метрики
|
||||
возраста/решения модели вывода. В существующем fragment format нет времени съёмки.
|
||||
Готовый независимый аппаратный контроль возраста этим не доказан; формат в Lab051
|
||||
не расширялся. ID монотонны в одной конечной сессии; wrap/restart не реализованы.
|
||||
|
||||
Внешняя IQ-очередь также не реализована. Офлайн-источник подаёт следующий блок
|
||||
по запросу и физически не хранит многосекундный backlog. Если бы вход шёл непрерывно,
|
||||
максимальное отставание **32,632404 с** потребовало бы около **2088473848 байт**
|
||||
дополнительного complex64 IQ. Поэтому bounded RX state нельзя выдавать за готовую
|
||||
ограниченную live-систему: при таких затратах потребуются потери/переполнение.
|
||||
REAL-TIME CAPABLE = NO.
|
||||
|
||||
В длительном опыте встречено значительно больше фоновых кандидатов, чем в коротком:
|
||||
147082 против 2374. Кэш дал 135057 повторных использований, но осталось 12239 новых
|
||||
окон синхронизации. Следующее ограничение — стоимость этих фоновых событий.
|
||||
Одного ускорения Viterbi недостаточно; подгонять отсев по TX запрещено.
|
||||
|
||||
## Регрессии и независимая проверка
|
||||
|
||||
38 целевых тестов: весь диапазон timing, ненулевые и нулевые ties, локальные
|
||||
DC/края общего RRC, точная децимация при движении окна, Viterbi noisy/tie/invalid,
|
||||
совпадение старого/нового блочного приёма, усечение, буферы и finally.
|
||||
Verifier нашёл два случая округления FFT (timing и refine); оба исправлены,
|
||||
воспроизведены повторно и закреплены тестами. Adversary отдельно проверил
|
||||
malformed PHY (0 body/Viterbi), дубликаты и старые ID, лимиты объекта,
|
||||
усечение и восстановление глобальных подмен.
|
||||
|
||||
Сохранённая Lab047-запись 20 ксимв/с повторно обработана штатным Lab048:
|
||||
**36/36, FEC/CRC PASS, JPEG SHA MATCH**, 19 ошибок до FEC, 0 после.
|
||||
Исходный путь и SHA JPEG сохранены в regression20k/result.json.
|
||||
Старый режим работает прежним декодером; адаптер Lab051 принимает только 500 ксимв/с
|
||||
и не подменяет аппаратную проверку этого режима.
|
||||
|
||||
Итог: quick_gate PASS — 688 тестов, 17 функциональных проверок, 13 штатных пропусков.
|
||||
Полный pytest — 688 passed за 72,92 с, 0 failed; одно предупреждение доступа к
|
||||
`.pytest_cache`. git diff --check PASS. Подробности — в записи 040 журнала.
|
||||
Управление ELRS/CRSF, локальная защита и ядро protocol не менялись.
|
||||
|
||||
## Файлы и воспроизведение
|
||||
|
||||
- [Потоковый RX](../experiments/lab051_streaming_receiver.py)
|
||||
- [Профилировщик](../experiments/lab051_receiver_profile.py)
|
||||
- [Сценарии и событийная шкала](../experiments/lab051_receiver_performance.py)
|
||||
- [Тесты](../tests/test_lab051_streaming_receiver.py)
|
||||
- [Baseline](../data/processed/lab051/baseline_profile.json)
|
||||
- [Три коротких замера и профиль](../data/processed/lab051/benchmark.json)
|
||||
- [Основной поток и все 739 блоков](../data/processed/lab051/stream.json)
|
||||
- [Длинный тест памяти](../data/processed/lab051/memory.json)
|
||||
- [Наблюдение массивов](../data/processed/lab051/replay.json)
|
||||
- [20 ксимв/с](../data/processed/lab051/regression20k/result.json)
|
||||
|
||||
```powershell
|
||||
python experiments/lab051_receiver_profile.py
|
||||
python experiments/lab051_receiver_performance.py --stage all
|
||||
python -m pytest tests/test_lab051_streaming_receiver.py -q -p no:cacheprovider
|
||||
python tools/quick_gate.py
|
||||
python -m pytest tests/ -q
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Старая запись:
|
||||
|
||||
```powershell
|
||||
python experiments/lab048_receiver_performance.py --run-saved-capture --capture 'C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\rx_stream.cs8' --original-result 'C:\Users\user\Documents\SDR_Rover_Lab047_raw\20260903_115747\independent_result.json' --tx-image data/processed/lab047/tx_image.jpg --output-directory data/processed/lab051/regression20k
|
||||
```
|
||||
|
||||
Локальные replay .npy — ограниченные окна, не полная IQ-запись; в Git не добавлены.
|
||||
Новый полный CS8/IQ файл не создавался. Lab051 git add/commit и push не выполнялись.
|
||||
Следующий рекомендуемый этап — отдельное программное исследование стоимости
|
||||
уникальных фоновых кандидатов в полном режиме 5 кадров/с с тем же PHY/FEC/CRC.
|
||||
RF, QPSK, новый FEC/кодек, 180 МГц и аппаратные 500 ксимв/с не выполнялись. STOP.
|
||||
Reference in New Issue
Block a user