Перейти до основного вмісту

Вузли ядер LiDAR і BEV

SimaVoxelize, SimaPillarScatter, SimaBEVPoolPlan і SimaBEVPool виконують етапи PointPillars і BEVFusion, що не працюють на MLA. Кожен вузол виконує одну операцію бібліотеки ядер SiMa на вибраній вами цілі. Вузол ніколи не переходить на іншу ціль і не копіює тензор, щоб змінити його розміщення. Якщо бібліотека ядер відхиляє запит, побудова графа завершується помилкою з поясненням від бібліотеки ядер.

ВузолОпераціяЦілі
SimaVoxelizeТочки LiDAR у декорований квантований тензор стовпцівA65, EV74
SimaPillarScatterОзнаки стовпців у щільне полотно BEVA65, EV74
SimaBEVPoolPlanКалібрування камер у план пулінгу BEVA65
SimaBEVPoolОзнаки глибини й контексту камер у сітку BEVA65, EV74

Це фрагмент побудови графа для використання всередині застосунку. Реалізуйте read_sweep() у застосунку: функція має повертати непорожній вектор із 4*N значень float у порядку x, y, z, часовий зсув. Значення N має вміщуватися в INT32. Ця функція не належить до API Neat. Фрагмент лише будує граф: він не подає кадр і не отримує вихідний результат.

#include <neat.h>

using namespace simaai::neat;

SimaVoxelizeOptions voxelize;
voxelize.voxel_size = {0.25f, 0.25f, 8.0f};
voxelize.point_cloud_range = {-50.0f, -50.0f, -5.0f, 50.0f, 50.0f, 3.0f};
voxelize.quantization.multiplier = 2.720007f; // 1 / the consuming MLA input's scale
voxelize.quantization.zero_point = 8;
voxelize.max_pillars = 40000;
voxelize.max_points_per_pillar = 64;
voxelize.target = SimaKernelTarget::EV74;

// One sample holds every input tensor of the node, in order.
InputOptions input;
input.payload_type = PayloadType::Tensor;
input.memory_policy = InputMemoryPolicy::Ev74;
input.caps_override = "application/vnd.simaai.tensor, representation=(string)tensor-set, "
"storage=(string)tensorbuffer";

std::vector<float> xyzt = read_sweep(); // x, y, z, time offset per point
const auto n = static_cast<std::int64_t>(xyzt.size() / 4);
auto points = Tensor::from_vector(xyzt, {n, 4}, TensorMemory::EV74);
auto point_count = Tensor::from_vector(std::vector<std::int32_t>{static_cast<std::int32_t>(n)},
{1}, TensorMemory::EV74);

Graph graph;
graph.add(nodes::Input(input));
graph.add(nodes::SimaVoxelize(voxelize));
graph.add(nodes::Output());
auto run = graph.build(TensorList{points, point_count});

Python надає ті самі типи параметрів і фабрики pyneat.nodes.sima_voxelize, pyneat.nodes.sima_pillar_scatter, pyneat.nodes.sima_bev_pool_plan і pyneat.nodes.sima_bev_pool.

Контракти входів і виходів​

Кожен вузол читає тензори свого вхідного зразка в наведеному нижче порядку й публікує свої виходи в наведеному нижче порядку. Кількість тензорів, типи даних і форми перевіряються під час побудови графа.

ВузолВходиВиходи
SimaVoxelizeFP32 points [N,4] (x, y, z, часовий зсув); INT32 point_count [1]INT8 або BF16 decorated [1,M,T,10]; INT32 coordinates [M,2] як (row, column); INT32 count [1]
SimaPillarScatterINT8 або BF16 features [M,C]; INT32 coordinates [M,2]; INT32 count [1]canvas [1,H,W,C] того самого типу, що й features
SimaBEVPoolPlanFP32 camera2lidar, camera_intrinsics і img_aug_matrix [N,4,4]; FP32 lidar_aug_matrix [4,4]UINT8 plan [P]
SimaBEVPoolINT8 або BF16 depth; context того самого типу; UINT8 plan [P]INT8 або BF16 bev [1,nZ*C,nX,nY]
  • SimaVoxelize читає лише перші point_count точок. max_pillars (M) і max_points_per_pillar (T) — фіксовані місткості; точки й стовпці понад них відкидаються. Невикористані рядки містять нульову точку квантування та нульові координати.
  • SimaPillarScatter читає лише перші count стовпців. Він також приймає ознаки з додатковими осями розміру 1, наприклад [1,M,1,C] від моделі ознак стовпців. C має бути кратним 16. Порожні комірки містять zero_point; це має бути код INT8, який вхідне квантування моделі, що читає полотно, призначає значенню 0.0. Байти полотна розміщені в щільному порядку NHWC (для полотна INT8).
  • SimaBEVPoolPlan бере свою зрізану піраміду огляду (xs, ys, ds) і сітку (bx, dx, nx) з перетворення вигляду контрольної точки. Типовий SimaBEVPoolPlanFormat::V1 групує точки за комірками BEV; для SimaBEVPool на EV74 використовуйте SimaBEVPoolPlanFormat::V2 разом із context_channels. Типово P — розмір плану в найгіршому випадку; байти після використаної довжини плану не записуються.
  • SimaBEVPool підсумовує depth x context за точками плану кожної комірки й квантує суму за допомогою quantization. Задайте cells рівним nx плану, plan_format — format плану, а для входів INT8 quantization.multiplier — depth_scale * context_scale / output_scale. З типовим SimaBEVPoolStorage::ChannelsLast входи надходять як [N,fH,fW,D] і [N,fH,fW,C], як їх створюють моделі камер. Вихід публікується як логічний [1,nZ*C,nX,nY] із кроками «канали останніми», тож його байти мають розміщення NHWC [1,nX,nY,nZ*C]. SimaBEVPoolStorage::Contiguous використовує входи [N,D,fH,fW] і [N,C,fH,fW] та щільний вихід.

Логічні осі та фізичне розміщення​

SimaBEVPool розрізняє порядок логічних осей і фізичний порядок байтів. Тензор із позначкою TensorLayout::HWC має логічні осі [N,H,W,K], а з позначкою TensorLayout::CHW — [N,K,H,W]. Вузол надає перевагу цій позначці й перевіряє відповідність кроків значенню input_storage. Для ChannelsLast входи HWC мають бути щільними, а входи CHW — мати кроки з каналами останніми. Для Contiguous потрібні щільні входи CHW; позначка HWC відхиляється. Той самий канонічний порядок осей можна задати через Tensor.axis_semantics; якщо вказано обидві анотації, вони мають узгоджуватися.

Для входів ChannelsLast без позначки зберігається правило сумісності: щільний NHWC або нещільний логічний NCHW із кроками «канали останніми». Якщо виміри розміру 1 роблять логічне подання NCHW також щільним, явно задайте Tensor.layout = TensorLayout::CHW. У цьому випадку самі кроки не визначають логічні осі. Виходи Pool мають логічні осі CHW і кроки відповідно до output_storage; вузол не транспонує й не копіює дані для зміни розміщення.

Метадані квантування​

Виходи INT8 містять своє квантування в Tensor.semantic.quant, коли вузол його знає: scale = 1 / quantization.multiplier для SimaVoxelize і для SimaBEVPool із входами BF16; depth_scale * context_scale / quantization.multiplier для SimaBEVPool, обидва входи INT8 якого мають масштаб; квантування ознак для SimaPillarScatter. В інших випадках квантування не публікується.

Для входів INT8 у Scatter поканальне квантування підтримується лише на останній осі ознак C, зокрема для форм на зразок [1,M,1,C]. Задайте C скінченних додатних масштабів і C нульових точок INT8, кожна з яких дорівнює параметру Scatter zero_point для заповнення порожніх комірок. Полотно зберігає ці значення, а вісь квантування змінюється на 3 у [1,H,W,C]. Інші осі квантування відхиляються: параметри осі стовпців не можуть описувати фіксоване полотно після розподілу стовпців за координатами.

SimaBEVPool не підтримує метадані поканального квантування й відхиляє їх, а не мовчки видаляє. Поведінка наявних потензорних метаданих і явно заданих нульових точок не змінюється; відсутні вхідні метадані залишаються невідомими.

Тензори BF16​

  • SimaVoxelize з output_dtype = TensorDType::BFloat16 округлює кожне значення декорації binary32 до найближчого парного BF16, зберігає кожне NaN як 0x7FC0 і заповнює порожні слоти значенням +0.0. Залиште quantization типовим; будь-яке інше значення відхиляється.
  • SimaPillarScatter зберігає тип ознак. Для BF16 потрібен zero_point = 0, тому порожні комірки містять +0.0. Кожна комірка полотна BF16 зберігає молодші байти C каналів, а потім старші. semantic.tess явно описує це фізичне розміщення: slice_shape = [1,1,1,C], axes = [0,1,2,3], format = "MLA_TILED_BF16_COMPACT"; storage_bytes — перевірений розмір даних. Плитки D/H/W/C впорядковано за C/D/H/W, дані всередині — за D/H/W/C. BF16 використовує окремі молодші й старші байти кожної комірки; плитки вирівняно до 16 байтів. copy_dense_bytes_tight(), типізований data_ptr() і DLPack відхиляють це нещільне розміщення. copy_payload_bytes() читає лише визначений діапазон збережених байтів. clone() і вихід Owned зберігають байти та метадані без перетворення плиток. Наступні вузли, які розуміють таке розміщення, наприклад SimaArgMax, безпосередньо отримують перевірений опис виробника в тому самому графі. Споживачі лише щільних даних і непідтримувані плиткові входи графа його відхиляють. Повноканальні плитки-комірки INT8 побайтово тотожні щільному NHWC, тому звичайне щільне копіювання та DLPack працюють як раніше. Давні позначки TessSpec лише з форматом не змінюють поведінки; ненульове storage_bytes означає явне плиткове сховище.
  • SimaBEVPoolPlan приймає input_dtype — тип входів SimaBEVPool. План V2 розподіляє кеші EV74 у байтах, тож план BF16 може бути більшим. План V1 однаковий для обох типів.
  • SimaBEVPool приймає depth і context у BF16 з нульовими точками 0. Сума кожної комірки обчислюється в binary32 у порядку точок плану, тож плани V1 і V2 можуть дати різні байти BF16. bev у BF16 зберігає кожну суму, округлену до найближчого парного, з NaN як 0x7FC0, і потребує типового quantization. bev у INT8 із входів BF16 квантує суму: задайте quantization.multiplier рівним 1 / output_scale. Входи INT8 не можуть дати bev у BF16.

Додані поля TessSpec::axes і TessSpec::storage_bytes мають типові значення, тому наявні агрегатні ініціалізатори з двома полями зберігають сумісність вихідного коду. Бінарне компонування публічної структури змінюється: перебудуйте споживачів C++ і прив’язки Python з узгодженими заголовками та бібліотеками Core; не використовуйте повторно бінарні файли, зібрані з попереднім компонуванням.

Під’єднайте вузли до конвеєра​

Ці вузли — звичайні етапи графа, але їхні входи надходять від кількох попередніх етапів:

  • У PointPillars SimaPillarScatter потребує ознак стовпців від моделі ознак стовпців разом із coordinates і count від SimaVoxelize.
  • У BEVFusion SimaBEVPool потребує depth і context від моделі камер разом із планом від SimaBEVPoolPlan.

Об’єднайте ці тензори в один вхідний зразок у наведеному вище порядку перед вузлом. Наприклад, отримайте обидва результати у своєму застосунку й надішліть їх разом до графа вузла.

SimaPillarScatter копіює байти ознак без повторного квантування, тож ознаки вже мають використовувати вхідне квантування моделі, що читає полотно. SimaBEVPool отримує всі камери в одному тензорі [N,...].

Плани та стан​

План залежить лише від калібрування та SimaBEVPoolPlanOptions. Із cache_plan = true кадр, байти калібрування якого збігаються з попереднім кадром, копіює план попереднього кадру замість того, щоб обчислювати його знову. Кеш зберігається в контексті бібліотеки ядер, яким середовище виконання володіє для кожного кадру в обробці, тому влучання можливе, але не гарантоване, коли одночасно обробляються кілька кадрів. Байти плану однакові в обох випадках.

SimaBEVPool не зберігає стану між кадрами. Під час кожного виконання він перевіряє заголовок і вміст плану та відхиляє план, що не відповідає його формам.

Обмеження цілей​

EV74 додає обмеження до контракту A65:

  • SimaBEVPool потребує context із розміщенням «канали останніми» та кількістю каналів, кратною 16, і bev із розміщенням «канали останніми». План V2 також потребує depth із розміщенням «канали останніми».
  • SimaBEVPoolPlan виконується лише на A65.

Запит, який ціль не підтримує, завершується помилкою на цій цілі. Вузол не вибирає замість вас іншу ціль.