mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-07 22:59:00 -10:00
Correctness (class E): kept even without a speedup. Flag B (PR #5096): the graph waits for "flag B >= layer + 1" and flag B is one counter that only rises. A non-empty plan raises it from a host function on the copy stream, after its uploads. An empty plan (n <= 0) raised it on the spot, even when the copy stream still held an earlier layer's, or the other token group's, uploads: the counter jumped past a layer whose bytes had not landed. Verifier::raise_flag_b_ordered() now raises it through the copy stream whenever copies were queued in this window, and directly only when none were. The same ordering is used at the four places that publish an empty plan when the pool did not (run, run_slot_rows, batch_serve, service), which had the same hazard. A failed host-function launch used to be ignored (flag B never rose and the window hung); it now stops the engine like a failed upload does, after releasing the GPU waits. Adaptive-copy bookkeeping (PR #4844): a round planned on the pipelined loop whose loop drained before step 3 is marked resident on the serial rule, which used the lo/hi/bi of an earlier round, so only that range of swaps was marked and the others were lost (the slot held the new expert, the table still named the old one). The pipelined plan now names the whole round. The range arithmetic moved into AdaptiveCopyRange (adaptive_range.hpp) so a host test can walk the rounds. Tests: tests/core/flag_order_test.cu (delayed host-function copy standing in for a slow DMA, a consumer kernel that reads staging once flag B allows it, a negative control that shows the old direct raise releases it early, flag only rises, nothing queued, graph replay twice, every device; passes on both A4000s of z440), tests/program/ adaptive_range_test.cpp (serial batches for 0, 1, many swaps and several caps, the stale-range loss, empty round). Co-authored-by: AlanJager <[email protected]> Co-authored-by: uvlad23 <[email protected]>
103 lines
4.5 KiB
C++
103 lines
4.5 KiB
C++
// The adaptive tier's copy-in range (PR #4844): which swaps of a round step 3 marks resident. No GPU: the rounds are
|
|
// walked as generate.cpp walks them, and each swap is a counter that says whether it was copied in and whether it was marked.
|
|
#include <cstdio>
|
|
#include <vector>
|
|
|
|
#include "strata/program/adaptive_range.hpp"
|
|
|
|
using strata::program::AdaptiveCopyRange;
|
|
|
|
static int failed = 0;
|
|
#define CHECK(x) do { if (!(x)) { std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #x); ++failed; } } while (0)
|
|
|
|
struct Round {
|
|
size_t total;
|
|
std::vector<int> copied, marked;
|
|
explicit Round(size_t n) : total(n), copied(n, 0), marked(n, 0) {}
|
|
static bool all(const std::vector<int>& v) { for (int x : v) if (x != 1) return false; return true; }
|
|
};
|
|
|
|
// the serial loop: plan (clear + next_batch), copy the batch, step 3 marks it, repeat while more() (the arena's cap)
|
|
static void serial_round(AdaptiveCopyRange& r, Round& rd, size_t batch) {
|
|
r.clear();
|
|
r.next_batch(rd.total, batch);
|
|
for (;;) {
|
|
for (size_t i = r.lo; i < r.hi; ++i) ++rd.copied[i];
|
|
for (size_t i = r.landed_begin(false); i < r.landed_end(false, rd.total); ++i) ++rd.marked[i];
|
|
if (!r.more(rd.total)) break;
|
|
r.next_batch(rd.total, batch);
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
// the serial loop covers every swap once, whatever the cap, including zero swaps and one swap
|
|
for (size_t total : {(size_t) 0, (size_t) 1, (size_t) 2, (size_t) 5, (size_t) 8, (size_t) 64})
|
|
for (size_t batch : {(size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7, (size_t) 8, (size_t) 100}) {
|
|
AdaptiveCopyRange r;
|
|
Round rd(total);
|
|
serial_round(r, rd, batch);
|
|
CHECK(Round::all(rd.copied) && Round::all(rd.marked));
|
|
CHECK(r.bi == total && r.hi == total);
|
|
}
|
|
|
|
// a batch never runs past the list, even for a cap near the size_t limit
|
|
{
|
|
AdaptiveCopyRange r;
|
|
r.next_batch(5, (size_t) -1);
|
|
CHECK(r.lo == 0 && r.hi == 5 && r.bi == 5);
|
|
r.next_batch(5, 2); // asked again after the end: an empty batch at the end
|
|
CHECK(r.lo == 5 && r.hi == 5 && !r.more(5));
|
|
}
|
|
|
|
// the lost experts of #4844: a round planned on the pipelined loop whose loop drained before step 3 is marked on the
|
|
// serial rule. An earlier serial round left [lo, hi) at its last batch.
|
|
{
|
|
AdaptiveCopyRange r;
|
|
Round first(5);
|
|
serial_round(r, first, 2); // leaves lo = 4, hi = 5, bi = 5
|
|
CHECK(r.lo == 4 && r.hi == 5);
|
|
|
|
// the old plan (nothing set for a pipelined round): step 3 on the serial rule marks only the stale range
|
|
{
|
|
AdaptiveCopyRange stale = r;
|
|
Round pl(7);
|
|
for (size_t i = 0; i < pl.total; ++i) ++pl.copied[i]; // a_in copied all of them at the gaps
|
|
for (size_t i = stale.landed_begin(false); i < stale.landed_end(false, pl.total); ++i) ++pl.marked[i];
|
|
CHECK(!Round::all(pl.marked)); // the bug this test pins
|
|
}
|
|
|
|
// the fix: the pipelined plan names the whole round
|
|
Round pl(7);
|
|
r.whole_round(pl.total);
|
|
for (size_t i = 0; i < pl.total; ++i) ++pl.copied[i];
|
|
for (size_t i = r.landed_begin(false); i < r.landed_end(false, pl.total); ++i) ++pl.marked[i]; // the drained tick: serial rule
|
|
CHECK(Round::all(pl.marked));
|
|
CHECK(!r.more(pl.total)); // and no second batch is posted
|
|
// the same round finished on the pipelined rule marks the same range
|
|
CHECK(r.landed_begin(true) == r.landed_begin(false) && r.landed_end(true, pl.total) == r.landed_end(false, pl.total));
|
|
}
|
|
|
|
// one swap, and an empty round, on the pipelined plan
|
|
{
|
|
AdaptiveCopyRange r;
|
|
r.next_batch(9, 4);
|
|
r.whole_round(1);
|
|
CHECK(r.landed_begin(false) == 0 && r.landed_end(false, 1) == 1 && !r.more(1));
|
|
r.whole_round(0);
|
|
CHECK(r.landed_begin(false) == 0 && r.landed_end(false, 0) == 0 && !r.more(0));
|
|
}
|
|
|
|
// a serial round that follows a pipelined one starts clean
|
|
{
|
|
AdaptiveCopyRange r;
|
|
r.whole_round(6);
|
|
Round rd(3);
|
|
serial_round(r, rd, 2);
|
|
CHECK(Round::all(rd.copied) && Round::all(rd.marked));
|
|
}
|
|
|
|
if (failed) { std::fprintf(stderr, "%d check(s) failed\n", failed); return 1; }
|
|
std::puts("adaptive_range_test: ok");
|
|
return 0;
|
|
}
|