// Standalone MLA attention experiment, not enabled in the inference engine. // Times dynamic BF16 KV staging, DMA sync, FP16 NPU QK/AV, CPU online softmax // and FP32 merge against the engine's four-A76 NEON attention algorithm. #include "core_workers.h" #include "mla_npu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Clock = std::chrono::steady_clock; using Json = nlohmann::json; constexpr int H = 16, D = 192, V = 128; double Ms(Clock::time_point t) { return std::chrono::duration(Clock::now()-t).count(); } void Check(int rc, const char * op) { if (rc != 0) throw std::runtime_error(std::string(op)+": "+std::to_string(rc)); } float Float(std::uint16_t x) { return std::bit_cast(std::uint32_t(x) << 16); } std::uint16_t Bf16(float x) { auto u = std::bit_cast(x); return (u + 0x7fffU + ((u >> 16) & 1U)) >> 16; } struct Data { int rows, history; std::vector q; std::vector k, v; Data(int m, int n): rows(m), history(n), q(m*H*D), k(std::size_t(n)*H*D), v(std::size_t(n)*H*V) {} }; void Randomize(Data & d, int seed) { std::mt19937 rng(seed); std::normal_distribution normal(0, 1); for (auto & x : d.q) x = normal(rng); for (auto & x : d.k) x = Bf16(normal(rng)); for (auto & x : d.v) x = Bf16(normal(rng)); } template void Read(std::ifstream & f, std::vector & x) { f.read(reinterpret_cast(x.data()), x.size()*sizeof(T)); if (!f) throw std::runtime_error("truncated snapshot"); } Data Load(const char * path) { std::ifstream f(path, std::ios::binary); std::array header {}; f.read(reinterpret_cast(header.data()), sizeof(header)); if (!f || header[0] != 0x31414c4d || header[1] < 1 || header[1] > 128 || header[2] < header[1] || header[2] > 65536 || header[3] != H) throw std::runtime_error("invalid MLA snapshot"); Data d(header[1], header[2]); Read(f,d.q); Read(f,d.k); Read(f,d.v); return d; } float Dot(const float * q, const std::uint16_t * k) { auto a = vdupq_n_f32(0), b = a; for(int i=0;i,4> scores; explicit Cpu(int n) { for(auto & x:scores) x.resize(n); } double Run(const Data & d, std::vector & out) { const auto start=Clock::now(); constexpr std::array cores {0,1,2,3}; ling3::CoreWorkers::Instance().Run(cores,[&](int c){ auto & s=scores[c]; for(int h=c;h::infinity(), sum=0; for(int t=0;t normal_a,normal_b; std::vector normal_c; Matmul(int rows,int inner,int columns,int core):m(rows),k(inner),n(columns),normal_a(m*k),normal_b(k*n),normal_c(m*n) { try { info.M=m;info.K=k;info.N=n; info.type=RKNN_FLOAT16_MM_FLOAT16_TO_FLOAT32; info.B_layout=RKNN_MM_LAYOUT_NATIVE;info.AC_layout=RKNN_MM_LAYOUT_NATIVE;info.iommu_domain_id=2; Check(rknn_matmul_create(&ctx,&info,&attr),"create FP16 matmul"); Check(rknn_matmul_set_core_mask(ctx,static_cast(1U<(a->virt_addr)[(kk/8*m+row)*8+kk%8]=x; else normal_a[row*k+kk]=x; } void B(int kk,int col,float x) { if(direct)static_cast<__fp16 *>(b->virt_addr)[((col/16*(k/32)+kk/32)*16+col%16)*32+kk%32]=x; else normal_b[kk*n+col]=x; } float C(int row,int col) const { return direct?static_cast(c->virt_addr)[(col/4*m+row)*4+col%4]:normal_c[row*n+col]; } void Run(Timing & s, bool qk) { auto t=Clock::now(); if(!direct) { auto * pa=static_cast<__fp16 *>(a->virt_addr); for(int kk=0;kkvirt_addr,k,n,&info),"pack native B"); } s.stage+=Ms(t);t=Clock::now(); Check(rknn_mem_sync(ctx,a,RKNN_MEMORY_SYNC_TO_DEVICE),"sync A"); Check(rknn_mem_sync(ctx,b,RKNN_MEMORY_SYNC_TO_DEVICE),"sync dynamic B"); s.sync+=Ms(t); t=Clock::now(); Check(rknn_matmul_run(ctx),"run matmul"); (qk?s.qk:s.av)+=Ms(t); t=Clock::now(); Check(rknn_mem_sync(ctx,c,RKNN_MEMORY_SYNC_FROM_DEVICE),"sync C");s.sync+=Ms(t); t=Clock::now();const auto * pc=static_cast(c->virt_addr); if(!direct) { for(int nn=0;nnvirt_addr,0,a->size);std::memset(b->virt_addr,0,b->size);Timing s;Run(s,true); } }; struct Lane { Matmul qk,av; std::vector maximum,denominator,factor,accumulator; Timing timing; Lane(int rows,int tile,int core):qk(rows,D,tile,core),av(rows,tile,V,core),maximum(rows),denominator(rows),factor(rows),accumulator(rows*V) {} void Head(const Data & d,int h,int tile,std::vector & out) { std::fill(maximum.begin(),maximum.end(),-std::numeric_limits::infinity()); std::fill(denominator.begin(),denominator.end(),0);std::fill(accumulator.begin(),accumulator.end(),0); auto t=Clock::now(); for(int r=0;r,3> lanes; Npu(int rows,int block):tile(block) { for(int c=0;c<3;++c)lanes[c]=std::make_unique(rows,tile,c); } double Run(const Data & d,std::vector & out) { auto t=Clock::now();constexpr std::array cores {0,1,2}; ling3::CoreWorkers::Instance().Run(cores,[&](int c){ auto & l=*lanes[c];l.timing={};for(int h=c;h & a,const std::vector & b) { double aa=0,bb=0,ab=0,ss=0,mx=0; for(std::size_t i=0;i128||n65536||tile<32||tile>4096||tile%32||repeats<1||repeats>10)throw std::invalid_argument("invalid dimensions"); Data data=argc==6?Load(argv[5]):Data(m,n); if(data.rows!=m||data.history!=n)throw std::invalid_argument("snapshot dimensions differ"); if(argc==5)Randomize(data,12345); std::vector cpu(m*H*V),npu(cpu.size());Cpu reference(n); if(std::getenv("LING3_MLA_PROBE_INTEGRATED")) { ling3::MlaNpu backend;backend.Prepare(m); const double cpu_ms=reference.Run(data,cpu);const auto started=Clock::now(); if(!backend.Run(data.q,data.k,data.v,m,n,npu))throw std::runtime_error("integrated NPU path did not run"); const double npu_ms=Ms(started);const auto error=Error(npu,cpu); std::cout<()<0.001?0:1; } auto begin=Clock::now();Npu candidate(m,tile);double setup=Ms(begin); for(auto & l:candidate.lanes){l->qk.Warmup();l->av.Warmup();} // Also warms the persistent worker pool. Neither path's setup is in timings. const double first=candidate.Run(data,npu); const double cpu_warmup=reference.Run(data,cpu); Json results=Json::array(); for(int r=0;rtiming.json()); auto error=Error(npu,cpu); results.push_back({{"cpu_ms",cpu_ms},{"npu_cpu_ms",npu_ms},{"speedup",cpu_ms/npu_ms},{"error",error},{"lane_work_ms",lanes}}); if(error["cosine"].get()<0.999 || error["relative_l2"].get()>0.02) throw std::runtime_error("attention accuracy failed: "+results.dump()); } std::cout<qk.direct}, {"setup_ms",setup},{"warmup_attention_ms",first},{"cpu_warmup_ms",cpu_warmup},{"runs",results}}).dump()<