Skip to content

npu: the Whisper front-end four f32 host kernels (first slice of #1931, refs #1783) - #2154

Closed
bong-water-water-bong wants to merge 1 commit into
mainfrom
main-audio-kernels
Closed

bong-water-water-bong wants to merge 1 commit into
mainfrom
main-audio-kernels

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

The first slice of #1931, split out so it can actually be reviewed. #1931 has been open all night with 15 files and no verdict — a fair outcome for a 2,206-line PR during a release — so this is the part that stands alone: four f32 host kernels, known-answer tested, device-free, ~200 lines.

Refs #1783 (Whisper on the NPU, F8).

npu/forward/include/audio_ops.h — header-only, no dependencies:

kernel why it is here
conv1d the encoder's mel front-end is two strided 3-tap convs ([1280,128,3] s1 p1, then [1280,1280,3] s2 p1); weight layout (cout,cin,k) as the ONNX initializer carries it, so no transpose at the call site
layer_norm biased variance, eps under the root, null affine defaults
gelu_erf the export is approximate=none; the tanh form is 1.7e-4 away at x=2, and the test asserts the two are distinguishable at 1e-6
softmax_row subtracts the row max, because the encoder's logits reach 100+ once the 1/sqrt(d) scale is folded in

Every expected value was computed in double from the formulas the encoder reference uses (tools/whisper_encoder_ref.py, itself validated at max|d| 1.469e-3 / rel 7.549e-5 with identical transcripts on three fixtures). Arms: the real shapes including 3000 -> 1500 for the stride-2 conv, a no-bias/stride-1/pad-0 edge, a constant row normalising to zero, null gamma/beta defaults, softmax shift-invariance at +1000, and the tanh-vs-erf discriminator.

One arm failed on first run — my typed 1.3416270 against the true 1.34163542 for the null-affine case — which is the test catching its own author; fixed to the computed constant.

What is NOT here: the graph reader, the executor, the mel front-end, the tool. They stay in #1931, which I will re-open as a follow-up once this lands (or close in favour of smaller pieces, whichever the reviewers prefer). Nothing in this PR needs a device, a container or a model.

ctest -R audio_ops passes; fresh configure rc 0.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at b3b7962: CHANGES

Reviewed head b3b7962 (refs #1783). This is the first verdict.

This slice is already in an open PR that has been reviewed. #2133 ("the Whisper host reference (#1931) with its blockers cleared, onto main", head 7e10fcc) carries all of #1931 onto main, including both of this PR's files. It has 2 VERDICT reviews at that head, mine among them (READY, with the audio tests run from an NPU build and mutants of the fork-join). So the body's premise, that #1931's work has had no verdict, is out of date: #2133 superseded #1931 and #2035 earlier tonight.

This PR's files are #2133's files with the licence notice taken back out. git diff 7e10fcc b3b7962:

  • on npu/forward/include/audio_ops.h: 12 deletions, all of them the Apache notice;
  • on tests/audio_ops_test.cpp: the same 12 lines.

The code is otherwise identical. Fixing those notices was one of #2133's fixes. Without them:

$ tools/copyright.py --check
missing copyright notice: npu/forward/include/audio_ops.h
missing copyright notice: tests/audio_ops_test.cpp

That is the red build at this head (failed step "Copyright notice on every file"). The apt retry message in that log is not the failure.

Also, the two PRs cannot both land. Whichever merges second adds files the first already created, with different headers, and conflicts on both.

Suggested: close this in favour of #2133, or ask on #2133 for it to be split if the reviewers there want smaller pieces. If this split is kept instead, take both files from #2133 as they are, with the full notice. The rest checked out: git merge-tree onto main is clean today (#2133 has not merged), and the content matches #2133's reviewed version.

@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

1783 - Partially compliant

Compliant requirements:

  • Implement the Whisper front-end four f32 host kernels (conv1d, layer_norm, gelu_erf, softmax_row)
  • Ensure correctness with known-answer tests
  • Run tests on device-free environment with CI build capability

Non-compliant requirements:

  • Measure Whisper encoder performance against FLM 1.0.12's claim of 0.6 s per 30 s window
  • Close the gap between current performance and FLM's 3× faster performance

Requires further human verification:

  • Performance measurement and gap closing verification
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Kernel Implementation Accuracy

The implementation of the conv1d kernel uses a nested loop structure that may not be optimal for performance on the NPU. While the correctness is verified through tests, the inner loop over kernel taps could be a bottleneck for real-time audio processing. Consider optimizing the memory access pattern or using SIMD instructions if available.

inline void conv1d(const float* x, int cin, int T, const float* w, const float* b, int cout, int k, int stride, int pad,
                   float* out) {
    const int tout = conv1d_out_len(T, k, stride, pad);
    for (int co = 0; co < cout; ++co) {
        for (int t = 0; t < tout; ++t) {
            float acc = (b != nullptr) ? b[co] : 0.0f;
            for (int ci = 0; ci < cin; ++ci) {
                const float* xr = x + static_cast<std::size_t>(ci) * T;
                const float* wr = w + (static_cast<std::size_t>(co) * cin + ci) * k;
                for (int j = 0; j < k; ++j) {
                    const int src = t * stride + j - pad;
                    if (src >= 0 && src < T) acc += wr[j] * xr[src];
                }
            }
            out[static_cast<std::size_t>(co) * tout + t] = acc;
        }
    }
}
Memory Access Pattern

The conv1d kernel accesses input data using xr[src] where xr is calculated as x + static_cast<std::size_t>(ci) * T. This pattern may not be cache-friendly for large inputs, potentially leading to performance degradation on the NPU. The memory layout and access pattern should be reviewed for optimal cache utilization.

const float* xr = x + static_cast<std::size_t>(ci) * T;
const float* wr = w + (static_cast<std::size_t>(co) * cin + ci) * k;
for (int j = 0; j < k; ++j) {
    const int src = t * stride + j - pad;
    if (src >= 0 && src < T) acc += wr[j] * xr[src];
}
Test Coverage

The test cases cover basic functionality but may not fully exercise edge cases such as very large inputs, extreme values, or boundary conditions. Additional test cases should be added to ensure robustness of the audio operations under various conditions.

// Copyright 2026 bong-water-water-bong
// SPDX-License-Identifier: Apache-2.0
//
// audio_ops_test.cpp -- known-answer checks for npu/forward/include/audio_ops.h. No container, no device, no pack: every
// expected value was computed in double in Python from the same formulas the Whisper encoder reference uses
// (tools/whisper_encoder_ref.py), so this binary runs wherever CI builds, which is the point.

#include <cmath>
#include <cstdio>
#include <string>
#include <vector>

#include "audio_ops.h"

namespace {

int g_failures = 0;

void check(bool cond, const std::string& what) {
    if (!cond) {
        std::printf("FAIL %s\n", what.c_str());
        ++g_failures;
    }
}

void expect_eq(int got, int want, const std::string& what) {
    if (got != want) {
        std::printf("FAIL %s: got %d want %d\n", what.c_str(), got, want);
        ++g_failures;
    }
}

void expect_close(float got, double want, double tol, const std::string& what) {
    const double d = std::fabs(static_cast<double>(got) - want);
    if (!(d <= tol)) {
        std::printf("FAIL %s: got %.10g want %.10g (|d| %.3g > %.3g)\n", what.c_str(), static_cast<double>(got), want, d,
                    tol);
        ++g_failures;
    }
}

// 1. The front-end conv, at the shapes the encoder actually uses: two strided 3-tap convs, pad 1 on both ends.
void conv1d_arms() {
    // cin 2, T 5, cout 3, k 3, stride 2, pad 1 -> tout 3
    const std::vector<float> x = {0.5f, -1.0f, 0.25f, 2.0f, -0.75f,   // channel 0
                                  1.5f, 0.0f, -0.5f, 1.0f, 0.25f};   // channel 1
    std::vector<float> w(3 * 2 * 3);
    for (int i = 0; i < 18; ++i) w[i] = static_cast<float>((i + 1) * 0.0625);
    const std::vector<float> b = {0.125f, -0.25f, 0.5f};

    expect_eq(onebit::audio::conv1d_out_len(5, 3, 2, 1), 3, "conv1d_out_len T5 k3 s2 p1");
    expect_eq(onebit::audio::conv1d_out_len(3000, 3, 2, 1), 1500, "conv1d_out_len 3000 -> 1500");

    std::vector<float> out(3 * 3);
    onebit::audio::conv1d(x.data(), 2, 5, w.data(), b.data(), 3, 3, 2, 1, out.data());

    // double, same formulas: [0.46875, 0.6875, 0.484375, 0.46875, 0.96875, 1.046875, 1.59375, 2.375, 2.734375]
    const double want[9] = {0.46875, 0.6875, 0.484375, 0.46875, 0.96875, 1.046875, 1.59375, 2.375, 2.734375};
    for (int i = 0; i < 9; ++i) expect_close(out[i], want[i], 1e-6, "conv1d value " + std::to_string(i));

    // edge case: stride 1, pad 0, k 3 -- the second conv's arithmetic without the padding, and a 1-tap full-window
    // where tout * stride + k - pad == T exactly (no tail row may be emitted past the input)
    std::vector<float> out2(2);
    onebit::audio::conv1d(x.data(), 1, 5, w.data(), nullptr, 1, 3, 1, 0, out2.data());
    expect_close(out2[0], 0.03076171875 * 0.0 + 0.0625 * 0.5 + 0.125 * -1.0 + 0.1875 * 0.25, 1e-6, "conv1d no bias");
    expect_eq(onebit::audio::conv1d_out_len(5, 3, 1, 0), 3, "conv1d_out_len T5 k3 s1 p0");
}

// 2. Layer norm: biased variance, eps added under the root, gamma/beta applied after the normalise.
void layernorm_arm() {
    const std::vector<float> v = {0.25f, -1.5f, 2.0f, 0.75f};
    const std::vector<float> g = {1.0f, 0.5f, -1.0f, 2.0f};
    const std::vector<float> be = {0.0f, 1.0f, 0.25f, -0.5f};
    std::vector<float> out(4);
    onebit::audio::layer_norm(v.data(), g.data(), be.data(), 4, 1e-5f, out.data());
    // double: [-0.0995034038, 0.2537244718, -1.0435442489, 0.0970204226]
    const double want[4] = {-0.0995034038, 0.2537244718, -1.0435442489, 0.0970204226};
    for (int i = 0; i < 4; ++i) expect_close(out[i], want[i], 1e-6, "layer_norm value " + std::to_string(i));

    // A constant row normalises to exactly beta whatever eps is: the variance is 0, so the numerator is 0 too.
    const std::vector<float> flat = {2.0f, 2.0f, 2.0f, 2.0f};
    onebit::audio::layer_norm(flat.data(), nullptr, nullptr, 4, 1e-5f, out.data());
    for (int i = 0; i < 4; ++i) expect_close(out[i], 0.0, 1e-7, "layer_norm flat " + std::to_string(i));
    // ... and with no gamma/beta the default is identity scale, zero shift.
    std::vector<float> out2(4);
    const std::vector<float> v2 = {1.0f, 2.0f, 3.0f, 4.0f};
    onebit::audio::layer_norm(v2.data(), nullptr, nullptr, 4, 1e-5f, out2.data());
    expect_close(out2[0], -1.34163542, 1e-6, "layer_norm null gamma/beta");
}

// 3. erf-GELU -- the export is approximate=none, so tanh must not pass this.
void gelu_arm() {
    // double: -0.0455002639, 0.3457312306, 2.9959503059
    expect_close(onebit::audio::gelu_erf(-2.0f), -0.0455002639, 1e-6, "gelu(-2)");
    expect_close(onebit::audio::gelu_erf(0.5f), 0.3457312306, 1e-6, "gelu(0.5)");
    expect_close(onebit::audio::gelu_erf(3.0f), 2.9959503059, 1e-6, "gelu(3)");

    // The two properties that separate erf-GELU from tanh-GELU: GELU(x) - GELU(-x) == x exactly, and the tanh form is
    // 1.7e-4 away at x = 2 -- far outside the 1e-6 this test runs at, so a tanh substitute cannot pass silently.
    expect_close(onebit::audio::gelu_erf(2.0f) - onebit::audio::gelu_erf(-2.0f), 2.0, 1e-6, "gelu odd part");
    const float tanh_form = 0.5f * 2.0f * (1.0f + std::tanh(0.7978845608f * (2.0f + 0.044715f * 8.0f)));
    check(std::fabs(tanh_form - onebit::audio::gelu_erf(2.0f)) > 1e-5, "tanh GELU is distinguishable from erf GELU");
}

// 4. Softmax: normalised, row-max stable, and order-preserving.
void softmax_arm() {
    std::vector<float> s = {1.0f, -2.0f, 0.5f, 0.25f};
    onebit::audio::softmax_row(s.data(), 4);
    // double: [0.4697737513, 0.0233886579, 0.2849321833, 0.2219054075]
    const double want[4] = {0.4697737513, 0.0233886579, 0.2849321833, 0.2219054075};
    for (int i = 0; i < 4; ++i) expect_close(s[i], want[i], 1e-6, "softmax value " + std::to_string(i));
    double sum = 0.0;
    for (float v : s) sum += v;
    expect_close(static_cast<float>(sum), 1.0, 1e-6, "softmax sums to 1");

    // +1000 on every logit must not change the result: the encoder's attention logits reach this range after the
    // 1/sqrt(d) scale is dropped on a long window.
    std::vector<float> big = {1001.0f, 998.0f, 1000.5f, 1000.25f};
    onebit::audio::softmax_row(big.data(), 4);
    for (int i = 0; i < 4; ++i) expect_close(big[i], want[i], 1e-6, "softmax shift-invariance " + std::to_string(i));
}

}  // namespace

int main() {
    conv1d_arms();
    layernorm_arm();
    gelu_arm();
    softmax_arm();
    if (g_failures != 0) {
        std::printf("audio ops: %d failures\n", g_failures);
        return 1;
    }
    std::printf("audio ops: conv1d (strided + padded + edge), layer_norm (biased var, null affine), gelu (erf), softmax "
                "(stable, shift-invariant) -- all pass\n");
    return 0;
}

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at b3b7962 — and the reason for the split is the most useful sentence in the body.

**The first slice of #1931, split out so it can actually be reviewed.** #1931 has been open all night with 15 files and
no verdict — a fair outcome for a 2,206-line PR during a release — so this is the part that sta[nds alone]

Why that is the right diagnosis of the review problem, not just the code

A 2,206-line PR during a release does not get reviewed; it gets deferred, and the deferral looks like diligence. Saying "a fair outcome" rather than blaming the reviewers is both accurate and useful: the fix is to make the unit reviewable, and this PR does it — the four f32 Whisper front-end kernels, each with its own arms, no route and no wiring.

What I would want from the slice

  • the split preserves the instrument: npu: the Whisper front-end four f32 host kernels, known-answer tested #1931's earlier head carried a --self-test judging the four failure signals; if those arms live in this slice, say so, and if they landed with a later one, say which — a slice that is reviewable but un-instrumented is the half that gets merged and then trusted;
  • the slicing map, in one line each: this PR = kernels + arms, next = front-end wiring, next = the route. Three lines tell the row's lead how far from done npu: the Whisper front-end four f32 host kernels, known-answer tested #1931 is, which is what a release needs from a 15-file PR being dismantled;
  • the known-answer provenance from the earlier head (A&S 7.1.26 for erf, |err| < 1.5e-7, because numpy has no exact erf) — that belongs in this slice's test header, since these are the kernels those numbers pin.

On the release timing

Splitting a 2,206-line PR at this hour is the right call and it costs a round of reviews; if the row's lead needs #1931 for the build, the honest note is which slice is on the critical path versus which can land after. That distinction is worth one sentence here, because "first slice" does not say whether the rest is blocking.

Nothing else: four kernels, arms, no route — the reviewable unit.

bong-water-water-bong pushed a commit that referenced this pull request Nov 11, 2026
… one

Measured on the shape #2199 names: a header keeping only `// Copyright ...` and
`// SPDX-License-Identifier: Apache-2.0`.

  $ copyright.py --check     copyright + SPDX present but the licence block is not: short.h
  $ copyright.py --fix       added: short.h
  $ copyright.py --check     copyright notice duplicated inside the header window: short.h   rc 1

--fix prepended the canonical block and LEFT the two stub lines, so the file came out
with the Copyright/SPDX pair twice, `duplicated_notice()` fired, and the check stayed
red. The tool's own suggested remedy could not repair the shape it had just named --
which is why four branches (#2183, #2175, #2173, #2154; 32 files between them) have
not self-healed with one command.

`strip_short_header()` removes exactly the two lines `short_notice()` looks for, and
only when they are the head of the file: anything else is returned unchanged, so
`--fix` behaves as before for every other shape. The file's own header text and a
leading `#!` are kept.

After: --fix -> "added: short.h"; --check rc 0; one SPDX line; the block present; the
description line intact.

Test: five arms in tests/copyright_check_untracked_test.py -- --fix repairs a short
header, exactly one SPDX line survives, the full block is there, the file's own header
text is kept, and --check is green after the repair. That file: 23 arms, 0 failures.
`copyright.py --check` on this tree rc 0 (nothing that was green became red).

Refs #2199, #2175, #2183, #2154.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

ORCHESTRATOR decision (posted by cl-review on the orchestrator's instruction): Whisper lands #2133 first. The stack #2154 → #2173 → #2175 → #2183 then rebases onto #2133 (or onto main once #2133 is in) and drops everything that duplicates it: audio_ops, audio_exec, audio_mel, onnx_graph, their tests and tools/whisper_transcribe.cpp as #2133 has them. What survives is what #2133 does not have: npu/audio_pack.{h,cpp}, tests/audio_pack_test.cpp, and #2183's pack-directory form of whisper_transcribe, applied to #2133's copy of the tool. The surviving files need the full Apache notice and add_dependencies(onebit audio_pack_test). A stack PR left with nothing of its own after the rebase closes.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Absorbed into #2183, which is now based on main — the lane is at the PR cap (16:45Z: at most two open PRs, and no new ones while main is red), and a four-deep stack of stacked PRs is exactly the fragmentation the cap exists to remove.

This PR's commit and everything it depended on are in #2183 (7 commits, 2,347 lines, all 16 files): kernels → graph reader + executor → log-mel front-end → the wav→ids tool and the pack container. Nothing is withdrawn; the reviewable surface is one PR with the whole host chain in it, and the device arm's preconditions are checkable from it (audio_kernel_shapes --kernels).

bong-water-water-bong added a commit that referenced this pull request Nov 12, 2026
…2199)

* copyright: a short header is named as one, not as a missing notice

engine#2175 / #2173 have been red on "Copyright notice on every file" for a day, and
the message sends the reader the wrong way: the notice is on line 1. What those files
keep is `// Copyright ...` and `// SPDX-License-Identifier: Apache-2.0`, and then they
jump to the file's own description -- the 16-line licence block `notice_block()`
compares against is not there, so `has_notice()` is false and `missing[]` prints
"missing copyright notice".

Measured on the two heads, same work, four lines apart:

  #2175 audio_mel.h line 4:  // audio_mel.h -- the log-mel front-end: hann-windowed STFT ...
  #2133 audio_mel.h line 4:  // Licensed under the Apache License, Version 2.0 (the "License");

#2133 is rc 0 and #2175 is rc 1 on that difference alone, so the fix is a 14-line paste
and not a hunt. `short_notice()` names the shape:

  copyright + SPDX present but the licence block is not (paste the 16-line form): <path>

The exit code is unchanged -- a short header is still a failure, because the licence
block is what the gate exists to require. Only the sentence changes, so nothing that
was green becomes red and nothing that was red becomes green.

Test: three arms in tests/copyright_check_untracked_test.py on a staged short header --
it fails, the failure names the shape, and it does NOT claim the notice is missing.
Verified: that test 18 arms / 0 failures; `copyright.py --check` on this tree rc 0;
and the predicate on the real files (short.h -> has_notice False / short_notice True,
full.h -> has_notice True / short_notice False).

Refs #2175, #2173, #2133.

* copyright: --fix replaces a short header instead of stacking a second one

Measured on the shape #2199 names: a header keeping only `// Copyright ...` and
`// SPDX-License-Identifier: Apache-2.0`.

  $ copyright.py --check     copyright + SPDX present but the licence block is not: short.h
  $ copyright.py --fix       added: short.h
  $ copyright.py --check     copyright notice duplicated inside the header window: short.h   rc 1

--fix prepended the canonical block and LEFT the two stub lines, so the file came out
with the Copyright/SPDX pair twice, `duplicated_notice()` fired, and the check stayed
red. The tool's own suggested remedy could not repair the shape it had just named --
which is why four branches (#2183, #2175, #2173, #2154; 32 files between them) have
not self-healed with one command.

`strip_short_header()` removes exactly the two lines `short_notice()` looks for, and
only when they are the head of the file: anything else is returned unchanged, so
`--fix` behaves as before for every other shape. The file's own header text and a
leading `#!` are kept.

After: --fix -> "added: short.h"; --check rc 0; one SPDX line; the block present; the
description line intact.

Test: five arms in tests/copyright_check_untracked_test.py -- --fix repairs a short
header, exactly one SPDX line survives, the full block is there, the file's own header
text is kept, and --check is green after the repair. That file: 23 arms, 0 failures.
`copyright.py --check` on this tree rc 0 (nothing that was green became red).

Refs #2199, #2175, #2183, #2154.

* copyright: --fix finds a short header behind a shebang

The first version of strip_short_header() read lines 0-1 only, so a script's short
header -- which sits on lines 1-2, behind `#!` -- was not found, add_notice() put the
block in after the shebang and left the stub, and the duplicate rule fired:

  $ copyright.py --check   copyright + SPDX present but the licence block is not: short.sh
  $ copyright.py --fix     added: short.sh
  $ copyright.py --check   copyright notice duplicated inside the header window: short.sh   rc 1

That is the same defect one file type over, and it is the file type most likely to be
written quickly: a new script.

Now the two lines are located after a leading `#!` and the shebang is carried through.
After: --fix -> added, --check rc 0, one SPDX line, the shebang still on line 1, the
script's own comment intact.

Five arms in tests/copyright_check_untracked_test.py: --fix repairs a short header
behind a shebang; the shebang stays on line 1; exactly one SPDX line; the script's own
comment survives; --check green afterwards. That file: 28 arms, 0 failures.
`copyright.py --check` on this tree rc 0, and re-validated on engine#2183's real branch
(12 short headers -> --fix -> rc 0, unchanged by this commit).

Refs #2199, #2183.

* copyright: drop strip_short_header, now that main's --fix does that job

Answers the CHANGES at 751aa35. Main has taken two --fix changes since this branch
was cut:

- #2192 (83012c8) repairs a short header, including one behind a `#!` -- `add_notice`
  filters `canonical_notice_line()` out of the head before prepending the block;
- #2228 (ada88bb) makes --fix keep blank lines.

Merged on top of those, this branch's `strip_short_header()` was redundant and
actively wrong: its `if rest and rest[0].strip() == "" and len(rest) > 1: rest = rest[1:]`
dropped a blank line after the stub, undoing #2228. The reviewer measured both:

| file | main | this head (before) |
|---|---|---|
| blank.sh (`#!` + stub + blank + code) | blank kept | blank dropped |
| blank.h (stub + blank + `#pragma once`) | blank kept | blank dropped |

So the function and its call are gone. What this PR keeps is the half main does not
have: `short_notice()` and the sentence it prints --

    copyright + SPDX present but the licence block is not (paste the 16-line form)

-- which is why the PR exists at all: main's `copyright.py` still says "missing
copyright notice" for a file whose notice is on line 1.

Re-ran the reviewer's four-case table at this head: `short.h`, `tool.py` (#! + # stub),
`blank.sh` (#! + stub + blank), `blank.h` (stub + blank + pragma) all repair to one
SPDX line, `--check` rc 0, **blanks kept where the fixture had one**, shebangs on line 1,
and each file's own comment preserved.

tests/copyright_check_untracked_test.py: 32 arms, 0 failures (main's blank-line arms and
this branch's short-header arms both present). copyright.py --check on this tree: rc 0.

---------

Co-authored-by: bong-water-water-bong <bong@water>
Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT: CHANGES at b3b7962

#2199 merged, so this is now one command rather than a paste -- and I ran the merged tool on this branch's head to confirm.

Measured at this head with origin/main:tools/copyright.py (the merged one): 2 short-header path(s) — files that carry // Copyright and // SPDX and not the 16-line licence block. The
sweep after #2199 landed, across the four branches that had this shape:

PR short-header paths
#2183 6
#2175 2
#2173 9
#2154 2

The command, verified on #2154 (the smallest one) rather than assumed:

$ python3 tools/copyright.py --fix
added: npu/forward/include/audio_ops.h
added: tests/audio_ops_test.cpp
$ python3 tools/copyright.py --check     rc 0

Nothing else changed, no description lines lost. The same run on #2183 cleared all six.

So: python3 tools/copyright.py --fix && python3 tools/copyright.py --check on this branch, and the copyright step goes green. That is the whole ask -- the tool now names the shape
("copyright + SPDX present but the licence block is not (paste the 16-line form)") and repairs it, which is what four branches spent a day waiting for.

Why still CHANGES: the 2 files are unmodified at this head, so build's copyright step fails on it. Nothing else in the tree is red as far as this lane can see.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant