Download src/runtime/fixed_schedule.cpp from Arm/Arm-Runtime-for-YOLO26-on-Raspberry-Pi-5: direct link, hf CLI and curl.
- Browser
- Download file 30.8 kB
-
https://huggingface.co/Arm/Arm-Runtime-for-YOLO26-on-Raspberry-Pi-5/resolve/main/src/runtime/fixed_schedule.cpp
- Command line
-
hf download hf://Arm/Arm-Runtime-for-YOLO26-on-Raspberry-Pi-5/src/runtime/fixed_schedule.cpp
-
curl -L -o fixed_schedule.cpp https://huggingface.co/Arm/Arm-Runtime-for-YOLO26-on-Raspberry-Pi-5/resolve/main/src/runtime/fixed_schedule.cpp
30.8 kB
| namespace { | |
| // ---- Tensor sizing and constant construction -------------------------------- | |
| enum class TensorShapeKind : uint8_t { | |
| kInvalid, | |
| kScalar, | |
| kFeatureMap, | |
| kAttentionChannels, | |
| kAttentionMatrix, | |
| kAnchors, | |
| kDetectionChannels, | |
| kDetections, | |
| }; | |
| struct TensorShape { | |
| TensorShapeKind kind; | |
| uint16_t channels; | |
| uint8_t stride; | |
| }; | |
| constexpr TensorShape Scalar() { return {TensorShapeKind::kScalar, 1, 0}; } | |
| constexpr TensorShape FeatureMap(uint16_t channels, uint8_t stride) { | |
| return {TensorShapeKind::kFeatureMap, channels, stride}; | |
| } | |
| constexpr TensorShape AttentionChannels(uint16_t channels) { | |
| return {TensorShapeKind::kAttentionChannels, channels, 32}; | |
| } | |
| constexpr TensorShape AttentionMatrix() { return {TensorShapeKind::kAttentionMatrix, 0, 32}; } | |
| constexpr TensorShape DetectionChannels(uint16_t channels) { | |
| return {TensorShapeKind::kDetectionChannels, channels, 0}; | |
| } | |
| TensorShape ShapeOf(TensorId tensor) { | |
| switch (tensor) { | |
| case TensorId::kBackboneScale: | |
| case TensorId::kDetectionScale: | |
| case TensorId::kCenterDivisor: | |
| return Scalar(); | |
| case TensorId::kInputHalf: | |
| return FeatureMap(3, 1); | |
| case TensorId::kBackboneP3: | |
| return FeatureMap(128, 8); | |
| case TensorId::kP4Entry: | |
| case TensorId::kBackboneP4: | |
| case TensorId::kP4Fusion: | |
| case TensorId::kNeckP4: | |
| case TensorId::kDetectionP4Projection: | |
| case TensorId::kDetectionP4: | |
| case TensorId::kP5UpsampledProjection: | |
| return FeatureMap(128, 16); | |
| case TensorId::kP4EntrySkip: | |
| case TensorId::kP4EntryMain: | |
| case TensorId::kP4FusionSkip: | |
| case TensorId::kP4FusionMain: | |
| case TensorId::kP4Projection: | |
| case TensorId::kDetectionP4Skip: | |
| case TensorId::kDetectionP4Main: | |
| return FeatureMap(64, 16); | |
| case TensorId::kP5Entry: | |
| case TensorId::kBackboneAttentionEntry: | |
| case TensorId::kBackboneAttentionOutput: | |
| case TensorId::kDetectionP5Projection: | |
| case TensorId::kDetectionP5: | |
| return FeatureMap(256, 32); | |
| case TensorId::kP5EntrySkip: | |
| case TensorId::kP5EntryMain: | |
| case TensorId::kBackboneAttentionBypass: | |
| case TensorId::kBackboneAttentionResidual: | |
| case TensorId::kP5Projection: | |
| case TensorId::kDetectionP5Skip: | |
| case TensorId::kDetectionP5Main: | |
| case TensorId::kDetectionP5Residual: | |
| return FeatureMap(128, 32); | |
| case TensorId::kP3Fusion: | |
| case TensorId::kDetectionP3: | |
| case TensorId::kP4UpsampledProjection: | |
| return FeatureMap(64, 8); | |
| case TensorId::kP3FusionSkip: | |
| case TensorId::kP3FusionMain: | |
| return FeatureMap(32, 8); | |
| case TensorId::kBackboneQkv: | |
| case TensorId::kDetectionP5Qkv: | |
| return AttentionChannels(128); | |
| case TensorId::kBackboneQuery: | |
| case TensorId::kBackboneKey: | |
| case TensorId::kBackboneScaledQuery: | |
| case TensorId::kBackboneKeyCopy: | |
| case TensorId::kBackboneQueryCopy: | |
| case TensorId::kDetectionP5Query: | |
| case TensorId::kDetectionP5Key: | |
| case TensorId::kDetectionP5ScaledQuery: | |
| case TensorId::kDetectionP5KeyCopy: | |
| case TensorId::kDetectionP5QueryCopy: | |
| return AttentionChannels(32); | |
| case TensorId::kBackboneValue: | |
| case TensorId::kBackbonePositionalInput: | |
| case TensorId::kDetectionP5Value: | |
| case TensorId::kDetectionP5ValueCopy: | |
| return AttentionChannels(64); | |
| case TensorId::kBackboneAttentionMatrix: | |
| case TensorId::kBackboneAttentionMatrixCopy: | |
| case TensorId::kDetectionP5AttentionMatrix: | |
| case TensorId::kDetectionP5AttentionMatrixCopy: | |
| return AttentionMatrix(); | |
| case TensorId::kAnchors: | |
| return {TensorShapeKind::kAnchors, 2, 0}; | |
| case TensorId::kBoxRegression: | |
| return DetectionChannels(4); | |
| case TensorId::kBoxLower: | |
| case TensorId::kBoxUpper: | |
| return DetectionChannels(2); | |
| case TensorId::kDetectionsHalf: | |
| return {TensorShapeKind::kDetections, 0, 0}; | |
| default: | |
| return {TensorShapeKind::kInvalid, 0, 0}; | |
| } | |
| } | |
| size_t TensorBytes(TensorShape shape, uint32_t resolution, uint32_t class_count) { | |
| const size_t positions = yolo26::DetectionPositions(resolution); | |
| switch (shape.kind) { | |
| case TensorShapeKind::kInvalid: | |
| return 0; | |
| case TensorShapeKind::kScalar: | |
| return 2 * shape.channels; | |
| case TensorShapeKind::kFeatureMap: { | |
| const size_t side = resolution / shape.stride; | |
| return 2 * shape.channels * side * side; | |
| } | |
| case TensorShapeKind::kAttentionChannels: { | |
| const size_t side = resolution / shape.stride; | |
| return 4 * shape.channels * side * side; | |
| } | |
| case TensorShapeKind::kAttentionMatrix: { | |
| const size_t side = resolution / shape.stride; | |
| const size_t flattened = side * side; | |
| return 4 * flattened * flattened; | |
| } | |
| case TensorShapeKind::kAnchors: | |
| case TensorShapeKind::kDetectionChannels: | |
| return 2 * shape.channels * positions; | |
| case TensorShapeKind::kDetections: | |
| return 2 * (class_count + yolo26::kBoxCoordinateChannels) * positions; | |
| } | |
| return 0; | |
| } | |
| uint16_t HalfBits(float value) { | |
| const __fp16 half = static_cast<__fp16>(value); | |
| uint16_t bits; | |
| std::memcpy(&bits, &half, sizeof(bits)); | |
| return bits; | |
| } | |
| void* ResolveTensor(const ScheduleState& state, TensorId tensor, uint8_t* arena, const float* input, | |
| float* output) { | |
| switch (tensor) { | |
| case TensorId::kAnchorConstant: | |
| return const_cast<uint16_t*>(state.anchors.data()); | |
| case TensorId::kStrideConstant: | |
| return const_cast<uint16_t*>(state.strides.data()); | |
| case TensorId::kBackboneScaleConstant: | |
| case TensorId::kDetectionScaleConstant: | |
| return const_cast<float*>(&state.attention_scale); | |
| case TensorId::kCenterDivisorConstant: | |
| return const_cast<int64_t*>(&state.center_divisor); | |
| case TensorId::kInput: | |
| return const_cast<float*>(input); | |
| case TensorId::kOutput: | |
| return output; | |
| default: | |
| return arena + state.offsets[TensorIndex(tensor)]; | |
| } | |
| } | |
| void StoreFloatAsHalf(void* source, void* output) { | |
| float value; | |
| std::memcpy(&value, source, sizeof(value)); | |
| const uint16_t half = HalfBits(value); | |
| std::memcpy(output, &half, sizeof(half)); | |
| } | |
| void StoreInt64AsHalf(void* source, void* output) { | |
| int64_t value; | |
| std::memcpy(&value, source, sizeof(value)); | |
| const uint16_t half = HalfBits(static_cast<float>(value)); | |
| std::memcpy(output, &half, sizeof(half)); | |
| } | |
| void ConvertInputNchwFloatToNhwcHalf(const ScheduleState& state, const float* source, | |
| uint16_t* output) { | |
| const size_t plane = size_t{state.resolution} * state.resolution; | |
| for (size_t pixel = 0; pixel < plane; ++pixel) { | |
| for (size_t channel = 0; channel < 3; ++channel) { | |
| output[pixel * 3 + channel] = HalfBits(source[channel * plane + pixel]); | |
| } | |
| } | |
| } | |
| struct NearestWork { | |
| const uint16_t* source; | |
| uint16_t* output; | |
| size_t width; | |
| size_t channels; | |
| }; | |
| // The neck uses exact nearest-neighbor 2x upsampling. Keeping it here avoids | |
| // constructing an XNNPACK partition for a fixed, contiguous four-way copy. | |
| void NearestRows(void* opaque, size_t start, size_t count) { | |
| auto* work = static_cast<NearestWork*>(opaque); | |
| const size_t output_width = work->width * 2; | |
| for (size_t y = start; y < start + count; ++y) { | |
| for (size_t x = 0; x < work->width; ++x) { | |
| const auto* source = work->source + (y * work->width + x) * work->channels; | |
| auto* top = work->output + ((2 * y) * output_width + 2 * x) * work->channels; | |
| auto* bottom = top + output_width * work->channels; | |
| for (size_t channel = 0; channel < work->channels; channel += 8) { | |
| const uint16x8_t value = vld1q_u16(source + channel); | |
| vst1q_u16(top + channel, value); | |
| vst1q_u16(top + work->channels + channel, value); | |
| vst1q_u16(bottom + channel, value); | |
| vst1q_u16(bottom + work->channels + channel, value); | |
| } | |
| } | |
| } | |
| } | |
| void Nearest2x(const uint16_t* source, uint16_t* output, size_t side, size_t channels, | |
| pthreadpool_t threadpool) { | |
| NearestWork work{source, output, side, channels}; | |
| pthreadpool_parallelize_1d_tile_1d(threadpool, NearestRows, &work, side, 1, 0); | |
| } | |
| void ConvertDetectionsHalfToFloat(const ScheduleState& state, const uint16_t* input, | |
| float* output) { | |
| size_t index = 0; | |
| for (; index + 8 <= state.output_elements; index += 8) { | |
| const float16x8_t half = vreinterpretq_f16_u16(vld1q_u16(input + index)); | |
| vst1q_f32(output + index, vcvt_f32_f16(vget_low_f16(half))); | |
| vst1q_f32(output + index + 4, vcvt_f32_f16(vget_high_f16(half))); | |
| } | |
| for (; index < state.output_elements; ++index) { | |
| __fp16 half; | |
| std::memcpy(&half, input + index, sizeof(half)); | |
| output[index] = static_cast<float>(half); | |
| } | |
| } | |
| struct ExternalBinding { | |
| ExternalValueId external_id; | |
| TensorId tensor; | |
| }; | |
| // ---- Partition binding and layout bridges ----------------------------------- | |
| class ScheduleExecution { | |
| public: | |
| ScheduleExecution(const ScheduleState& state, | |
| const std::array<xnn_runtime_t, kGraphCount>& runtimes, | |
| pthreadpool_t threadpool, uint8_t* arena, uint16_t* projection_scratch, | |
| const float* input, float* output) | |
| : state_(state), | |
| runtimes_(runtimes), | |
| threadpool_(threadpool), | |
| arena_(arena), | |
| projection_scratch_(projection_scratch), | |
| input_(input), | |
| output_(output) {} | |
| // Keeping these shared operations out of line prevents GCC from duplicating | |
| // them throughout the fixed phase program. Their current code layout affects | |
| // measured Pi latency, so changes require new performance evidence. | |
| [[gnu::noinline]] void Copy(TensorId source, TensorId destination) const { | |
| std::memcpy(Tensor(destination), Tensor(source), state_.sizes[TensorIndex(destination)]); | |
| } | |
| void ConvertFloatToHalf(TensorId source, TensorId destination) const { | |
| StoreFloatAsHalf(Tensor(source), Tensor(destination)); | |
| } | |
| void ConvertInt64ToHalf(TensorId source, TensorId destination) const { | |
| StoreInt64AsHalf(Tensor(source), Tensor(destination)); | |
| } | |
| void ConvertInputLayout() const { | |
| ConvertInputNchwFloatToNhwcHalf(state_, input_, | |
| static_cast<uint16_t*>(Tensor(TensorId::kInputHalf))); | |
| } | |
| void ConvertOutputType() const { | |
| ConvertDetectionsHalfToFloat( | |
| state_, static_cast<const uint16_t*>(Tensor(TensorId::kDetectionsHalf)), output_); | |
| } | |
| bool RunStemProjection() const { | |
| const size_t p2_pixels = size_t{state_.resolution / 4} * (state_.resolution / 4); | |
| const size_t p3_pixels = size_t{state_.resolution / 8} * (state_.resolution / 8); | |
| const ProjectionGraphs graphs{ | |
| runtimes_[GraphIndex(GraphId::kStemToP2)], | |
| runtimes_[GraphIndex(GraphId::kP2ToP3)], | |
| runtimes_[GraphIndex(GraphId::kP3ToP4)], | |
| }; | |
| const ProjectionDimensions dimensions{p2_pixels * 16, p3_pixels * 32, p2_pixels, p3_pixels}; | |
| const ProjectionBuffers buffers{ | |
| projection_scratch_, | |
| static_cast<uint16_t*>(Tensor(TensorId::kInputHalf)), | |
| static_cast<uint16_t*>(Tensor(TensorId::kBackboneP3)), | |
| static_cast<uint16_t*>(Tensor(TensorId::kP4Entry)), | |
| }; | |
| return RunProjectionOptimization(graphs, threadpool_, dimensions, buffers); | |
| } | |
| bool SplitContiguous(TensorId source, std::initializer_list<TensorId> destinations) const { | |
| return Split(source, destinations, 1); | |
| } | |
| bool SplitAttentionHeads(TensorId source, std::initializer_list<TensorId> destinations) const { | |
| // Q, K, and V are interleaved by head. The outer dimension keeps the two | |
| // heads separate while Split copies each head's contiguous channel ranges. | |
| return Split(source, destinations, 2); | |
| } | |
| bool SplitFeatureChannels(TensorId source, size_t source_channels, | |
| std::initializer_list<TensorId> destinations) const { | |
| const size_t source_bytes = state_.sizes[TensorIndex(source)]; | |
| const size_t bytes_per_position = source_channels * sizeof(uint16_t); | |
| if (source_bytes % bytes_per_position != 0) return false; | |
| return Split(source, destinations, source_bytes / bytes_per_position); | |
| } | |
| [[gnu::noinline]] bool RunPartition( | |
| GraphId graph, std::initializer_list<ExternalBinding> requested_bindings) const { | |
| std::array<xnn_external_value, 10> bindings{}; | |
| if (requested_bindings.size() > bindings.size()) return false; | |
| size_t index = 0; | |
| for (ExternalBinding binding : requested_bindings) { | |
| bindings[index++] = {binding.external_id.value, Tensor(binding.tensor)}; | |
| } | |
| xnn_runtime_t runtime = runtimes_[GraphIndex(graph)]; | |
| return runtime && xnn_reshape_runtime(runtime) == xnn_status_success && | |
| xnn_setup_runtime_v2(runtime, requested_bindings.size(), bindings.data()) == | |
| xnn_status_success && | |
| xnn_invoke_runtime(runtime) == xnn_status_success; | |
| } | |
| bool Upsample2x(TensorId source, TensorId destination, size_t channels) const { | |
| if (channels == 0) return false; | |
| const size_t source_bytes = state_.sizes[TensorIndex(source)]; | |
| const size_t bytes_per_pixel = channels * sizeof(uint16_t); | |
| if (source_bytes % bytes_per_pixel != 0) return false; | |
| const size_t pixels = source_bytes / bytes_per_pixel; | |
| const size_t side = static_cast<size_t>(std::sqrt(static_cast<double>(pixels))); | |
| if (side * side != pixels) return false; | |
| Nearest2x(static_cast<const uint16_t*>(Tensor(source)), | |
| static_cast<uint16_t*>(Tensor(destination)), side, channels, threadpool_); | |
| return true; | |
| } | |
| private: | |
| void* Tensor(TensorId tensor) const { | |
| return ResolveTensor(state_, tensor, arena_, input_, output_); | |
| } | |
| [[gnu::noinline]] bool Split(TensorId source, std::initializer_list<TensorId> destinations, | |
| size_t outer) const { | |
| if (outer == 0) return false; | |
| const size_t source_bytes = state_.sizes[TensorIndex(source)]; | |
| if (source_bytes % outer != 0) return false; | |
| const size_t source_stride = source_bytes / outer; | |
| size_t source_offset = 0; | |
| for (TensorId destination : destinations) { | |
| const size_t destination_bytes = state_.sizes[TensorIndex(destination)]; | |
| if (destination_bytes % outer != 0) return false; | |
| const size_t copy_bytes = destination_bytes / outer; | |
| auto* output = static_cast<uint8_t*>(Tensor(destination)); | |
| auto* input = static_cast<const uint8_t*>(Tensor(source)); | |
| for (size_t outer_index = 0; outer_index < outer; ++outer_index) { | |
| std::memcpy(output + outer_index * copy_bytes, | |
| input + outer_index * source_stride + source_offset, copy_bytes); | |
| } | |
| source_offset += copy_bytes; | |
| } | |
| return source_offset == source_stride; | |
| } | |
| const ScheduleState& state_; | |
| const std::array<xnn_runtime_t, kGraphCount>& runtimes_; | |
| pthreadpool_t threadpool_; | |
| uint8_t* arena_; | |
| uint16_t* projection_scratch_; | |
| const float* input_; | |
| float* output_; | |
| }; | |
| // ---- Fixed YOLO26n execution phases ----------------------------------------- | |
| bool RunInitialization(ScheduleExecution& execution) { | |
| // XNNPACK consumes these values as FP16 tensor inputs even though the | |
| // natural initialization arithmetic is FP32/integer. | |
| execution.Copy(TensorId::kAnchorConstant, TensorId::kAnchors); | |
| execution.ConvertFloatToHalf(TensorId::kBackboneScaleConstant, TensorId::kBackboneScale); | |
| execution.ConvertFloatToHalf(TensorId::kDetectionScaleConstant, TensorId::kDetectionScale); | |
| execution.ConvertInt64ToHalf(TensorId::kCenterDivisorConstant, TensorId::kCenterDivisor); | |
| // The public ABI is planar NCHW FP32; convolution partitions use NHWC FP16. | |
| execution.ConvertInputLayout(); | |
| return execution.RunStemProjection(); | |
| } | |
| bool RunBackbone(ScheduleExecution& execution) { | |
| // Move through the remaining backbone from P4 into P5 attention. Each | |
| // SplitContiguous call copies adjacent skip and main channel ranges into the | |
| // separate buffers consumed by the next XNNPACK graph. The attention entry | |
| // is likewise split into a preserved bypass and a residual branch; that | |
| // residual is projected into query, key, and value tensors, and the query is | |
| // scaled before matrix multiplication. Stop immediately if any copy or graph | |
| // invocation fails. | |
| if (!execution.SplitContiguous(TensorId::kP4Entry, | |
| {TensorId::kP4EntrySkip, TensorId::kP4EntryMain}) || | |
| !execution.RunPartition(GraphId::kBackboneP4ToP5, | |
| {{BackboneP4ToP5Bindings::kMainInput, TensorId::kP4EntryMain}, | |
| {BackboneP4ToP5Bindings::kSkipInput, TensorId::kP4EntrySkip}, | |
| {BackboneP4ToP5Bindings::kP4Output, TensorId::kBackboneP4}, | |
| {BackboneP4ToP5Bindings::kP5EntryOutput, TensorId::kP5Entry}}) || | |
| !execution.SplitContiguous(TensorId::kP5Entry, | |
| {TensorId::kP5EntrySkip, TensorId::kP5EntryMain}) || | |
| !execution.RunPartition(GraphId::kBackboneP5ToAttention, | |
| {{BackboneP5ToAttentionBindings::kMainInput, TensorId::kP5EntryMain}, | |
| {BackboneP5ToAttentionBindings::kSkipInput, TensorId::kP5EntrySkip}, | |
| {BackboneP5ToAttentionBindings::kAttentionEntryOutput, | |
| TensorId::kBackboneAttentionEntry}}) || | |
| !execution.SplitContiguous( | |
| TensorId::kBackboneAttentionEntry, | |
| {TensorId::kBackboneAttentionBypass, TensorId::kBackboneAttentionResidual}) || | |
| !execution.RunPartition( | |
| GraphId::kBackboneAttentionQkv, | |
| {{BackboneAttentionQkvBindings::kResidualInput, TensorId::kBackboneAttentionResidual}, | |
| {BackboneAttentionQkvBindings::kQkvOutput, TensorId::kBackboneQkv}}) || | |
| !execution.SplitAttentionHeads( | |
| TensorId::kBackboneQkv, | |
| {TensorId::kBackboneQuery, TensorId::kBackboneKey, TensorId::kBackboneValue}) || | |
| !execution.RunPartition( | |
| GraphId::kBackboneAttentionScale, | |
| {{BackboneAttentionScaleBindings::kQueryInput, TensorId::kBackboneQuery}, | |
| {BackboneAttentionScaleBindings::kScaleInput, TensorId::kBackboneScale}, | |
| {BackboneAttentionScaleBindings::kScaledQueryOutput, TensorId::kBackboneScaledQuery}})) { | |
| return false; | |
| } | |
| // These copies give the matrix and positional branches independent external | |
| // inputs. Their distinct lifetimes also let the tuned 640-pixel arena layout | |
| // reuse the original buffers after each consumer finishes. | |
| execution.Copy(TensorId::kBackboneKey, TensorId::kBackboneKeyCopy); | |
| execution.Copy(TensorId::kBackboneValue, TensorId::kBackbonePositionalInput); | |
| execution.Copy(TensorId::kBackboneScaledQuery, TensorId::kBackboneQueryCopy); | |
| if (!execution.RunPartition( | |
| GraphId::kBackboneAttentionMatrix, | |
| {{BackboneAttentionMatrixBindings::kKeyInput, TensorId::kBackboneKeyCopy}, | |
| {BackboneAttentionMatrixBindings::kQueryInput, TensorId::kBackboneQueryCopy}, | |
| {BackboneAttentionMatrixBindings::kMatrixOutput, TensorId::kBackboneAttentionMatrix}})) { | |
| return false; | |
| } | |
| execution.Copy(TensorId::kBackboneAttentionMatrix, TensorId::kBackboneAttentionMatrixCopy); | |
| return execution.RunPartition( | |
| GraphId::kBackboneAttentionOutput, | |
| {{BackboneAttentionOutputBindings::kValueInput, TensorId::kBackboneValue}, | |
| {BackboneAttentionOutputBindings::kPositionalInput, TensorId::kBackbonePositionalInput}, | |
| {BackboneAttentionOutputBindings::kMatrixInput, TensorId::kBackboneAttentionMatrixCopy}, | |
| {BackboneAttentionOutputBindings::kResidualInput, TensorId::kBackboneAttentionResidual}, | |
| {BackboneAttentionOutputBindings::kBypassInput, TensorId::kBackboneAttentionBypass}, | |
| {BackboneAttentionOutputBindings::kBackboneOutput, TensorId::kBackboneAttentionOutput}, | |
| {BackboneAttentionOutputBindings::kP5ProjectionOutput, TensorId::kP5Projection}}); | |
| } | |
| bool RunNeck(ScheduleExecution& execution) { | |
| if (!execution.Upsample2x(TensorId::kP5Projection, TensorId::kP5UpsampledProjection, 128) || | |
| !execution.RunPartition( | |
| GraphId::kNeckP4Fusion, | |
| {{NeckP4FusionBindings::kUpsampledP5Input, TensorId::kP5UpsampledProjection}, | |
| {NeckP4FusionBindings::kBackboneP4Input, TensorId::kBackboneP4}, | |
| {NeckP4FusionBindings::kFusionOutput, TensorId::kP4Fusion}}) || | |
| !execution.SplitContiguous(TensorId::kP4Fusion, | |
| {TensorId::kP4FusionSkip, TensorId::kP4FusionMain}) || | |
| !execution.RunPartition( | |
| GraphId::kNeckP4Block, | |
| {{NeckP4BlockBindings::kMainInput, TensorId::kP4FusionMain}, | |
| {NeckP4BlockBindings::kSkipInput, TensorId::kP4FusionSkip}, | |
| {NeckP4BlockBindings::kNeckP4Output, TensorId::kNeckP4}, | |
| {NeckP4BlockBindings::kP4ProjectionOutput, TensorId::kP4Projection}})) { | |
| return false; | |
| } | |
| return execution.Upsample2x(TensorId::kP4Projection, TensorId::kP4UpsampledProjection, 64) && | |
| execution.RunPartition( | |
| GraphId::kNeckP3Fusion, | |
| {{NeckP3FusionBindings::kUpsampledP4Input, TensorId::kP4UpsampledProjection}, | |
| {NeckP3FusionBindings::kFusionOutput, TensorId::kP3Fusion}, | |
| {NeckP3FusionBindings::kBackboneP3Input, TensorId::kBackboneP3}}) && | |
| execution.SplitFeatureChannels(TensorId::kP3Fusion, 64, | |
| {TensorId::kP3FusionSkip, TensorId::kP3FusionMain}); | |
| } | |
| bool RunDetectionPyramid(ScheduleExecution& execution) { | |
| return execution.RunPartition( | |
| GraphId::kDetectionP3Path, | |
| {{DetectionP3PathBindings::kMainInput, TensorId::kP3FusionMain}, | |
| {DetectionP3PathBindings::kSkipInput, TensorId::kP3FusionSkip}, | |
| {DetectionP3PathBindings::kNeckP4Input, TensorId::kNeckP4}, | |
| {DetectionP3PathBindings::kDetectionP3Output, TensorId::kDetectionP3}, | |
| {DetectionP3PathBindings::kP4ProjectionOutput, TensorId::kDetectionP4Projection}}) && | |
| execution.SplitContiguous(TensorId::kDetectionP4Projection, | |
| {TensorId::kDetectionP4Skip, TensorId::kDetectionP4Main}) && | |
| execution.RunPartition( | |
| GraphId::kDetectionP4Path, | |
| {{DetectionP4PathBindings::kMainInput, TensorId::kDetectionP4Main}, | |
| {DetectionP4PathBindings::kSkipInput, TensorId::kDetectionP4Skip}, | |
| {DetectionP4PathBindings::kBackboneP5Input, TensorId::kBackboneAttentionOutput}, | |
| {DetectionP4PathBindings::kDetectionP4Output, TensorId::kDetectionP4}, | |
| {DetectionP4PathBindings::kP5ProjectionOutput, TensorId::kDetectionP5Projection}}); | |
| } | |
| bool RunDetectionHead(ScheduleExecution& execution) { | |
| if (!execution.SplitFeatureChannels(TensorId::kDetectionP5Projection, 256, | |
| {TensorId::kDetectionP5Skip, TensorId::kDetectionP5Main}) || | |
| !execution.RunPartition( | |
| GraphId::kDetectionP5Qkv, | |
| {{DetectionP5QkvBindings::kMainInput, TensorId::kDetectionP5Main}, | |
| {DetectionP5QkvBindings::kResidualOutput, TensorId::kDetectionP5Residual}, | |
| {DetectionP5QkvBindings::kQkvOutput, TensorId::kDetectionP5Qkv}}) || | |
| !execution.SplitAttentionHeads( | |
| TensorId::kDetectionP5Qkv, | |
| {TensorId::kDetectionP5Query, TensorId::kDetectionP5Key, TensorId::kDetectionP5Value}) || | |
| !execution.RunPartition( | |
| GraphId::kDetectionP5AttentionScale, | |
| {{DetectionP5AttentionScaleBindings::kQueryInput, TensorId::kDetectionP5Query}, | |
| {DetectionP5AttentionScaleBindings::kScaleInput, TensorId::kDetectionScale}, | |
| {DetectionP5AttentionScaleBindings::kScaledQueryOutput, | |
| TensorId::kDetectionP5ScaledQuery}})) { | |
| return false; | |
| } | |
| // As in the backbone attention block, separate external buffers preserve | |
| // each branch until its final consumer and make the tuned 640-pixel layout | |
| // explicit rather than relying on an in-place XNNPACK operation. | |
| execution.Copy(TensorId::kDetectionP5Key, TensorId::kDetectionP5KeyCopy); | |
| execution.Copy(TensorId::kDetectionP5Value, TensorId::kDetectionP5ValueCopy); | |
| execution.Copy(TensorId::kDetectionP5ScaledQuery, TensorId::kDetectionP5QueryCopy); | |
| if (!execution.RunPartition( | |
| GraphId::kDetectionP5AttentionMatrix, | |
| {{DetectionP5AttentionMatrixBindings::kKeyInput, TensorId::kDetectionP5KeyCopy}, | |
| {DetectionP5AttentionMatrixBindings::kQueryInput, TensorId::kDetectionP5QueryCopy}, | |
| {DetectionP5AttentionMatrixBindings::kMatrixOutput, | |
| TensorId::kDetectionP5AttentionMatrix}})) { | |
| return false; | |
| } | |
| execution.Copy(TensorId::kDetectionP5AttentionMatrix, TensorId::kDetectionP5AttentionMatrixCopy); | |
| if (!execution.RunPartition( | |
| GraphId::kDetectionP5AndBoxes, | |
| {{DetectionP5AndBoxesBindings::kP3Input, TensorId::kDetectionP3}, | |
| {DetectionP5AndBoxesBindings::kP4Input, TensorId::kDetectionP4}, | |
| {DetectionP5AndBoxesBindings::kValueInput, TensorId::kDetectionP5Value}, | |
| {DetectionP5AndBoxesBindings::kValueCopyInput, TensorId::kDetectionP5ValueCopy}, | |
| {DetectionP5AndBoxesBindings::kMatrixInput, TensorId::kDetectionP5AttentionMatrixCopy}, | |
| {DetectionP5AndBoxesBindings::kResidualInput, TensorId::kDetectionP5Residual}, | |
| {DetectionP5AndBoxesBindings::kSkipInput, TensorId::kDetectionP5Skip}, | |
| {DetectionP5AndBoxesBindings::kMainInput, TensorId::kDetectionP5Main}, | |
| {DetectionP5AndBoxesBindings::kP5Output, TensorId::kDetectionP5}, | |
| {DetectionP5AndBoxesBindings::kBoxRegressionOutput, TensorId::kBoxRegression}}) || | |
| !execution.SplitContiguous(TensorId::kBoxRegression, | |
| {TensorId::kBoxLower, TensorId::kBoxUpper})) { | |
| return false; | |
| } | |
| if (!execution.RunPartition( | |
| GraphId::kClassificationAndDecode, | |
| {{ClassificationAndDecodeBindings::kP3Input, TensorId::kDetectionP3}, | |
| {ClassificationAndDecodeBindings::kP4Input, TensorId::kDetectionP4}, | |
| {ClassificationAndDecodeBindings::kP5Input, TensorId::kDetectionP5}, | |
| {ClassificationAndDecodeBindings::kAnchorsInput, TensorId::kAnchors}, | |
| {ClassificationAndDecodeBindings::kBoxLowerInput, TensorId::kBoxLower}, | |
| {ClassificationAndDecodeBindings::kBoxUpperInput, TensorId::kBoxUpper}, | |
| {ClassificationAndDecodeBindings::kCenterDivisorInput, TensorId::kCenterDivisor}, | |
| {ClassificationAndDecodeBindings::kStridesInput, TensorId::kStrideConstant}, | |
| {ClassificationAndDecodeBindings::kDetectionsOutput, TensorId::kDetectionsHalf}})) { | |
| return false; | |
| } | |
| // The fixed head emits contiguous FP16 [box coordinates, class scores]; the | |
| // public ABI exposes the same logical layout in FP32. | |
| execution.ConvertOutputType(); | |
| return true; | |
| } | |
| } // namespace | |
| // ---- Initialization and public schedule entry points ------------------------ | |
| bool InitializeSchedule(uint32_t resolution, uint32_t class_count, ScheduleState* state) { | |
| if (!state || !yolo26::IsSupportedResolution(resolution) || | |
| !yolo26::ClassDependentWeightBytesFit(class_count)) { | |
| return false; | |
| } | |
| *state = {}; | |
| state->resolution = resolution; | |
| state->class_count = class_count; | |
| state->attention_scale = 1.0F / std::sqrt(32.0F); | |
| const size_t positions = yolo26::DetectionPositions(resolution); | |
| state->anchors.reserve(2 * positions); | |
| state->strides.reserve(positions); | |
| for (uint32_t stride : yolo26::kDetectionStrides) { | |
| const uint32_t side = resolution / stride; | |
| for (uint32_t row = 0; row < side; ++row) { | |
| for (uint32_t column = 0; column < side; ++column) { | |
| state->anchors.push_back(HalfBits(static_cast<float>(column) + 0.5F)); | |
| } | |
| } | |
| } | |
| for (uint32_t stride : yolo26::kDetectionStrides) { | |
| const uint32_t side = resolution / stride; | |
| for (uint32_t row = 0; row < side; ++row) { | |
| for (uint32_t column = 0; column < side; ++column) { | |
| state->anchors.push_back(HalfBits(static_cast<float>(row) + 0.5F)); | |
| state->strides.push_back(HalfBits(static_cast<float>(stride))); | |
| } | |
| } | |
| } | |
| if (state->anchors.size() != 2 * positions || state->strides.size() != positions) { | |
| return false; | |
| } | |
| state->sizes[TensorIndex(TensorId::kAnchorConstant)] = state->anchors.size() * 2; | |
| state->sizes[TensorIndex(TensorId::kStrideConstant)] = state->strides.size() * 2; | |
| state->sizes[TensorIndex(TensorId::kBackboneScaleConstant)] = sizeof(float); | |
| state->sizes[TensorIndex(TensorId::kDetectionScaleConstant)] = sizeof(float); | |
| state->sizes[TensorIndex(TensorId::kCenterDivisorConstant)] = sizeof(int64_t); | |
| state->input_bytes = 3 * size_t{resolution} * resolution * sizeof(float); | |
| state->output_elements = (class_count + yolo26::kBoxCoordinateChannels) * positions; | |
| state->output_bytes = state->output_elements * sizeof(float); | |
| state->sizes[TensorIndex(TensorId::kInput)] = state->input_bytes; | |
| state->sizes[TensorIndex(TensorId::kOutput)] = state->output_bytes; | |
| for (size_t index = TensorIndex(TensorId::kAnchors); index < TensorIndex(TensorId::kOutput); | |
| ++index) { | |
| const auto tensor = static_cast<TensorId>(index); | |
| const size_t bytes = TensorBytes(ShapeOf(tensor), resolution, class_count); | |
| if (bytes == 0) return false; | |
| state->sizes[index] = bytes; | |
| } | |
| return yolo26::runtime_internal::PlanScheduleMemory(state); | |
| } | |
| bool ConfigureFixedProjection(const std::array<xnn_runtime_t, kGraphCount>& runtimes) { | |
| return ConfigureProjectionOptimization({ | |
| runtimes[GraphIndex(GraphId::kStemToP2)], | |
| runtimes[GraphIndex(GraphId::kP2ToP3)], | |
| runtimes[GraphIndex(GraphId::kP3ToP4)], | |
| }); | |
| } | |
| bool RunFixedSchedule(const ScheduleState& state, | |
| const std::array<xnn_runtime_t, kGraphCount>& runtimes, | |
| pthreadpool_t threadpool, uint8_t* arena, uint16_t* project, | |
| const float* input, float* output) { | |
| ScheduleExecution execution(state, runtimes, threadpool, arena, project, input, output); | |
| return RunInitialization(execution) && RunBackbone(execution) && RunNeck(execution) && | |
| RunDetectionPyramid(execution) && RunDetectionHead(execution); | |
| } | |
| bool OutputIsFinite(const ScheduleState& state, const float* output) { | |
| if (!output) return false; | |
| for (size_t index = 0; index < state.output_elements; ++index) { | |
| if (!std::isfinite(output[index])) return false; | |
| } | |
| return true; | |
| } | |