#include "zeroerr/assert.h" #include "zeroerr/unittest.h" #include "common/Exception.h" #include "tensor/Graph.h" #include "tensor/KernelGen.h" #include "tensor/Optimizer.h" #include "tensor/TF.h" #include "tensor/Tensor.h" #include "graphics/AmbientOcclusion.h" #include "graphics/AntiAliasing.h" #include "graphics/Canvas.h" #include "graphics/DrawItem2D.h" #include "graphics/Font.h" #include "graphics/GBuffer.h" #include "graphics/GlobalIllumination.h" #include "graphics/Graphics.h" #include "graphics/Grass.h" #include "graphics/Light.h" #include "graphics/Material.h" #include "graphics/Mesh.h" #include "graphics/Outline.h" #include "graphics/Quad.h" #include "graphics/RenderControl.h" #include "graphics/ScreenSpaceReflection.h" #include "graphics/Shader.h" #include "graphics/Texture.h" #include "graphics/Volumetric.h" #include "graphics/Water.h" #include "graphics/Waterfall.h" #include "window/Window.h" #include #include #include #include using namespace eve::tensor; namespace { /** GPU tensor tests need a live device; headless init is enough. */ void initHeadlessGfx(zeroerr::TestContext *_ZEROERR_TEST_CONTEXT) { auto *gfx = eve::graphics::Graphics::create(); REQUIRE(gfx != nullptr); gfx->initHeadless(320, 240); } } // namespace TEST_CASE("tensor.gpu.fusedElementwiseChain") { initHeadlessGfx(_ZEROERR_TEST_CONTEXT); auto *tf = TF::create(); std::unique_ptr fn(tf->func()); Tensor *x = fn->input2(4, 4); Tensor *y = tf->mulScalar(x, 2.f); y = tf->addScalar(y, 1.f); y = tf->relu(y); y = tf->mulScalar(y, 0.5f); fn->setOutput(y); std::unique_ptr compiled(fn->compile()); REQUIRE_EQ(compiled->getDevice(), std::string("gpu")); std::unique_ptr xd(tf->randomNormal2(4, 4)); std::unique_ptr out(compiled->run1(xd.get())); std::unique_ptr ref(xd->mulScalar(2.f)); ref.reset(ref->addScalar(1.f)); ref.reset(ref->relu()); ref.reset(ref->mulScalar(0.5f)); for (int i = 0; i < out->getSize(); ++i) REQUIRE(std::fabs(out->get(i) - ref->get(i)) < 1e-4f); } TEST_CASE("tensor.gpu.softmaxLayernormConv") { initHeadlessGfx(_ZEROERR_TEST_CONTEXT); auto *tf = TF::create(); std::unique_ptr w(tf->fill4(2, 1, 3, 3, 0.1f)); std::unique_ptr fn(tf->func()); Tensor *in = fn->input4(1, 1, 6, 6); Tensor *y = tf->conv2d(in, w.get(), 1, 0); y = tf->relu(y); y = tf->flatten(y); y = tf->layernorm(y, 1e-5f); fn->setOutput(y); std::unique_ptr compiled(fn->compile()); REQUIRE_EQ(compiled->getDevice(), std::string("gpu")); std::unique_ptr xd(tf->zeros4(1, 1, 6, 6)); for (int i = 0; i < xd->getSize(); ++i) xd->set(i, float((i % 7) - 3)); std::unique_ptr out(compiled->run1(xd.get())); std::unique_ptr ref(tf->conv2d(xd.get(), w.get(), 1, 0)); ref.reset(ref->relu()); ref.reset(ref->flatten()); ref.reset(tf->layernorm(ref.get(), 1e-5f)); REQUIRE_EQ(out->getSize(), ref->getSize()); for (int i = 0; i < out->getSize(); ++i) REQUIRE(std::fabs(out->get(i) - ref->get(i)) < 1e-3f); } TEST_CASE("tensor.gpu.transformerBlockInference") { initHeadlessGfx(_ZEROERR_TEST_CONTEXT); auto *tf = TF::create(); const int B = 1, T = 4, D = 8, C = 3; const float scale = 1.f / std::sqrt(float(D)); const float eps = 1e-5f; auto rand = [&](int d0, int d1, float s) { std::unique_ptr w(tf->randomNormal2(d0, d1)); w.reset(w->mulScalar(s)); return w; }; std::unique_ptr Wq(rand(D, D, 0.15f)); std::unique_ptr Wk(rand(D, D, 0.15f)); std::unique_ptr Wv(rand(D, D, 0.15f)); std::unique_ptr W1(rand(D, 2 * D, 0.15f)); std::unique_ptr W2(rand(2 * D, D, 0.15f)); std::unique_ptr Wc(rand(D, C, 0.15f)); std::unique_ptr xEager(tf->randomNormal3(B, T, D)); std::unique_ptr fn(tf->func()); Tensor *x = fn->input3(B, T, D); Tensor *l1 = tf->layernorm(x, eps); Tensor *l1m = tf->reshape2(l1, B * T, D); Tensor *qq = tf->reshape4(tf->matmul(l1m, Wq.get()), 1, 1, T, D); Tensor *kk = tf->reshape4(tf->matmul(l1m, Wk.get()), 1, 1, T, D); Tensor *vv = tf->reshape4(tf->matmul(l1m, Wv.get()), 1, 1, T, D); Tensor *a1 = tf->reshape3(tf->sdpa(qq, kk, vv, scale), B, T, D); Tensor *x1s = tf->add(x, a1); Tensor *l2 = tf->layernorm(x1s, eps); Tensor *l2m = tf->reshape2(l2, B * T, D); Tensor *hh = tf->matmul(l2m, W1.get()); hh = tf->gelu(hh); hh = tf->matmul(hh, W2.get()); Tensor *x2s = tf->add(x1s, tf->reshape3(hh, B, T, D)); Tensor *lg = tf->matmul(tf->reshape2(x2s, B * T, D), Wc.get()); fn->setOutput(tf->softmax(lg, 1)); std::unique_ptr compiled(fn->compile()); REQUIRE_EQ(compiled->getDevice(), std::string("gpu")); std::unique_ptr out(compiled->run1(xEager.get())); REQUIRE(std::fabs(out->reduceSum() - float(B * T)) < 1e-3f); std::unique_ptr action(tf->argmax(out.get(), 1, 0)); REQUIRE_EQ(action->getSize(), B * T); // cross-check the generated GPU kernels against the eager CPU reference std::unique_ptr ln1(tf->layernorm(xEager.get(), eps)); std::unique_ptr ln1m(tf->reshape2(ln1.get(), B * T, D)); std::unique_ptr q(tf->reshape4(tf->matmul(ln1m.get(), Wq.get()), 1, 1, T, D)); std::unique_ptr k(tf->reshape4(tf->matmul(ln1m.get(), Wk.get()), 1, 1, T, D)); std::unique_ptr v(tf->reshape4(tf->matmul(ln1m.get(), Wv.get()), 1, 1, T, D)); std::unique_ptr att(tf->reshape3(tf->sdpa(q.get(), k.get(), v.get(), scale), B, T, D)); std::unique_ptr x1(tf->add(xEager.get(), att.get())); std::unique_ptr ln2(tf->layernorm(x1.get(), eps)); std::unique_ptr ln2m(tf->reshape2(ln2.get(), B * T, D)); std::unique_ptr h(tf->matmul(ln2m.get(), W1.get())); h.reset(tf->gelu(h.get())); h.reset(tf->matmul(h.get(), W2.get())); std::unique_ptr x2(tf->add(x1.get(), tf->reshape3(h.get(), B, T, D))); std::unique_ptr logits(tf->matmul(tf->reshape2(x2.get(), B * T, D), Wc.get())); std::unique_ptr ref(tf->softmax(logits.get(), 1)); for (int i = 0; i < out->getSize(); ++i) REQUIRE(std::fabs(out->get(i) - ref->get(i)) < 1e-2f); } TEST_CASE("tensor.gpu.sdpaMatchesCpu") { initHeadlessGfx(_ZEROERR_TEST_CONTEXT); auto *tf = TF::create(); const int B = 1, H = 1, T = 4, S = 4, D = 8; std::unique_ptr q(tf->randomNormal4(B, H, T, D)); std::unique_ptr k(tf->randomNormal4(B, H, S, D)); std::unique_ptr v(tf->randomNormal4(B, H, S, D)); std::unique_ptr ref(tf->sdpa(q.get(), k.get(), v.get(), 0.353553f)); std::unique_ptr fn(tf->func()); Tensor *qq = fn->input4(B, H, T, D); Tensor *kk = fn->input4(B, H, S, D); Tensor *vv = fn->input4(B, H, S, D); fn->setOutput(tf->sdpa(qq, kk, vv, 0.353553f)); std::unique_ptr compiled(fn->compile()); REQUIRE_EQ(compiled->getDevice(), std::string("gpu")); std::unique_ptr out(compiled->run3(q.get(), k.get(), v.get())); for (int i = 0; i < out->getSize(); ++i) REQUIRE(std::fabs(out->get(i) - ref->get(i)) < 1e-4f); } namespace { void checkGpuGraphMatchesEager(zeroerr::TestContext *_ZEROERR_TEST_CONTEXT, TF *tf, const std::function &build, Tensor *feed, const char *name) { std::unique_ptr fn(tf->func()); Tensor *in = fn->input3(1, 4, 8); Tensor *outSym = build(in); fn->setOutput(outSym); std::unique_ptr compiled(fn->compile()); REQUIRE_EQ(compiled->getDevice(), std::string("gpu")); std::unique_ptr out(compiled->run1(feed)); std::unique_ptr ref(build(feed)); REQUIRE_EQ(out->getRank(), ref->getRank()); REQUIRE_EQ(out->getSize(), ref->getSize()); float worst = 0.f; for (int i = 0; i < out->getSize(); ++i) { const float error = std::fabs(out->get(i) - ref->get(i)); REQUIRE(std::isfinite(error)); worst = std::max(worst, error); } if (worst >= 1e-3f) fprintf(stderr, "stage %s worst=%.6g\n", name, double(worst)); REQUIRE_LT(worst, 1e-3f); } } // namespace TEST_CASE("tensor.gpu.stageBisect") { initHeadlessGfx(_ZEROERR_TEST_CONTEXT); auto *tf = TF::create(); const int T = 4, D = 8; std::unique_ptr x(tf->randomNormal3(1, T, D)); std::unique_ptr W(tf->randomNormal2(D, D)); auto stage1 = [&](Tensor *in) { Tensor *l = tf->layernorm(in, 1e-5f); Tensor *m = tf->reshape2(l, T, D); return tf->reshape4(tf->matmul(m, W.get()), 1, 1, T, D); }; auto stage2 = [&](Tensor *in) { Tensor *q = stage1(in); return tf->sdpa(q, q, q, 0.353553f); }; auto stage3 = [&](Tensor *in) { Tensor *a = stage2(in); return tf->add(in, tf->reshape3(a, 1, T, D)); }; auto stage4 = [&](Tensor *in) { Tensor *r = stage3(in); return tf->layernorm(r, 1e-5f); }; checkGpuGraphMatchesEager(_ZEROERR_TEST_CONTEXT, tf, stage1, x.get(), "stage1"); checkGpuGraphMatchesEager(_ZEROERR_TEST_CONTEXT, tf, stage2, x.get(), "stage2"); checkGpuGraphMatchesEager(_ZEROERR_TEST_CONTEXT, tf, stage3, x.get(), "stage3"); checkGpuGraphMatchesEager(_ZEROERR_TEST_CONTEXT, tf, stage4, x.get(), "stage4"); }