已合并
perf: conv1d_and_silu consume/produce [B,S,C] directly, drop transposes #476
Igor Prusov创建于 6 天前
perf: conv1d_and_silu consume/produce [B,S,C] directly, drop transposes #476
已合并
共 6 个文件变更+377-352
| @@ -8,22 +8,20 @@ | |||
| 8 | * if updateState: state = concat[..., -K:] | 8 | * if updateState: state = concat[..., -K:] |
| 9 | * | 9 | * |
| 10 | * No GM workspace concat. State/input are loaded into aligned UB float | 10 | * No GM workspace concat. State/input are loaded into aligned UB float |
| 11 | - * buffers. Outputs whose window lies entirely inside input (i >= K-1) are | 11 | + * buffers. |
| 12 | - * produced in full-width 64-lane blocks: each of the K taps is fetched with a | ||
| 13 | - * vgather (which accepts arbitrary byte-offset bases, unlike plain vector ops | ||
| 14 | - * that need 32B-aligned sources) and accumulated with vmuls+vadd, so 64 | ||
| 15 | - * outputs cost ~2K+ vector ops with fully-utilized lanes. Only the first K-1 | ||
| 16 | - * outputs (window straddling state and input) are computed via scalar moves. | ||
| 17 | - * GM copies use CopyGmToUbufAligned / CopyUbufToGmAligned so lengths need not | ||
| 18 | - * be 32B-aligned. | ||
| 19 | - * | ||
| 20 | - * Limits: kernelDim <= 16, seqLen (or per-request lens) <= 4096. | ||
| 21 | * | 12 | * |
| 22 | * Layouts: | 13 | * Layouts: |
| 23 | - * Uniform (seqLen != 0): input/output [B, C, S], state [B, C, K] | 14 | + * Uniform (seqLen != 0): input/output [B, S, C] (channel is the inner, |
| 24 | - * Packed (seqLen == 0): input/output [T, C] token-major, state still [B, C, K]; | 15 | + * contiguous dim), state [B, C, K], weight [C, 1, K]. |
| 25 | - * per-request tokens are input[queryStartLoc[b] : queryStartLoc[b]+queryLens[b]]. | 16 | + * Packed (seqLen == 0): input/output [T, C] token-major, state still [B, C, K]. |
| 26 | - * Host sets seqLen=0 for packed; do not test GM pointers against nullptr. | 17 | + * |
| 18 | + * The 64-lane (vectorized) dimension is the CHANNEL, not the sequence. Each tap | ||
| 19 | + * of the K-tap convolution is a contiguous, 32B-aligned 64-channel vector load | ||
| 20 | + * (vmul + vadd), instead of the per-tap vgather the [B,C,S] layout required. | ||
| 21 | + * Consuming/producing [B,S,C] directly also removes the two [B,S,C]<->[B,C,S] | ||
| 22 | + * Transpose passes around the kernel. | ||
| 23 | + * | ||
| 24 | + * Limits: kernelDim <= 16, seqLen (or per-request lens) <= 4096. | ||
| 27 | */ | 25 | */ |
| 28 | 26 | ||
| 29 | 27 | ||
| @@ -41,9 +39,9 @@ public: | |||
| 41 | static constexpr int kMaxKernel = 16; | 39 | static constexpr int kMaxKernel = 16; |
| 42 | static constexpr int kMaxInputF = 4096; | 40 | static constexpr int kMaxInputF = 4096; |
| 43 | static constexpr int kMaxBatchMeta = 256; | 41 | static constexpr int kMaxBatchMeta = 256; |
| 44 | - // Extra pad so full-width (64-lane) tail reads stay inside input_f. | 42 | + static constexpr int kBlock = VECTOR_MAX_NUM_OF_FP32; // 64 lanes |
| 45 | - static constexpr int kGatherPad = 128; | 43 | + static constexpr int kTile = 128; |
| 46 | - static constexpr int kBlock = VECTOR_MAX_NUM_OF_FP32; | 44 | + static constexpr int kWin = kMaxKernel - 1 + kTile; |
| 47 | 45 | ||
| 48 | __aicore__ inline XliteCausalConv1dSiLU() | 46 | __aicore__ inline XliteCausalConv1dSiLU() |
| 49 | { | 47 | { |
| @@ -70,44 +68,63 @@ public: | |||
| 70 | this->updateState = updateState; | 68 | this->updateState = updateState; |
| 71 | 69 | ||
| 72 | uint64_t off = 0; | 70 | uint64_t off = 0; |
| 73 | - // Aligned staging for GM<->UB dtype copies (must stay at offset 0 of a 32B region). | 71 | + // Aligned staging for GM<->UB dtype conversion round trips. |
| 74 | - // Sized for a single fp16/bf16 conversion round trip of the whole input. | ||
| 75 | stage_buf = reinterpret_cast<__ubuf__ Dtype *>((uintptr_t)off); | 72 | stage_buf = reinterpret_cast<__ubuf__ Dtype *>((uintptr_t)off); |
| 76 | - off += kMaxInputF * sizeof(Dtype); // 8KB (fp16/bf16) | 73 | + off += kMaxInputF * sizeof(Dtype); |
| 77 | - tmp_kernel_buf = reinterpret_cast<__ubuf__ Dtype *>((uintptr_t)off); | ||
| 78 | - off += 8 * 32; | ||
| 79 | 74 | ||
| 80 | - // fp32 weights (K floats) read into scalar registers once per channel. | 75 | + w_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); |
| 81 | - kernel_buf = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | 76 | + off += kBlock * kMaxKernel * sizeof(float); |
| 82 | - off += 8 * 32; | ||
| 83 | - | ||
| 84 | - state_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | ||
| 85 | - off += kMaxKernel * sizeof(float); // 64B, keep 32B-aligned | ||
| 86 | if (off % 32 != 0) { | 77 | if (off % 32 != 0) { |
| 87 | off = (off + 31) / 32 * 32; | 78 | off = (off + 31) / 32 * 32; |
| 88 | } | 79 | } |
| 89 | - input_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | 80 | + w_reorg = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); |
| 90 | - // Extra pad so full-width tap reads stay in bounds at the sequence tail. | 81 | + off += kBlock * kMaxKernel * sizeof(float); |
| 91 | - off += (kMaxInputF + kGatherPad) * sizeof(float); | 82 | + if (off % 32 != 0) { |
| 83 | + off = (off + 31) / 32 * 32; | ||
| 84 | + } | ||
| 85 | + state_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | ||
| 86 | + off += kBlock * kMaxKernel * sizeof(float); | ||
| 87 | + if (off % 32 != 0) { | ||
| 88 | + off = (off + 31) / 32 * 32; | ||
| 89 | + } | ||
| 90 | + state_reorg = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | ||
| 91 | + off += kBlock * kMaxKernel * sizeof(float); | ||
| 92 | + if (off % 32 != 0) { | ||
| 93 | + off = (off + 31) / 32 * 32; | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 97 | + win_raw = nullptr; | ||
| 98 | + } else { | ||
| 99 | + win_raw = reinterpret_cast<__ubuf__ Dtype *>((uintptr_t)off); | ||
| 100 | + off += kWin * kBlock * sizeof(Dtype); | ||
| 101 | + if (off % 32 != 0) { | ||
| 102 | + off = (off + 31) / 32 * 32; | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + window_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | ||
| 106 | + off += kWin * kBlock * sizeof(float); | ||
| 107 | + if (off % 32 != 0) { | ||
| 108 | + off = (off + 31) / 32 * 32; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + state_win = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | ||
| 112 | + off += kMaxKernel * kBlock * sizeof(float); | ||
| 92 | if (off % 32 != 0) { | 113 | if (off % 32 != 0) { |
| 93 | off = (off + 31) / 32 * 32; | 114 | off = (off + 31) / 32 * 32; |
| 94 | } | 115 | } |
| 95 | new_state_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | 116 | new_state_f = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); |
| 96 | - off += kMaxKernel * sizeof(float); | 117 | + off += kMaxKernel * kBlock * sizeof(float); |
| 97 | if (off % 32 != 0) { | 118 | if (off % 32 != 0) { |
| 98 | off = (off + 31) / 32 * 32; | 119 | off = (off + 31) / 32 * 32; |
| 99 | } | 120 | } |
| 100 | 121 | ||
| 101 | - off_ramp = reinterpret_cast<__ubuf__ uint32_t *>((uintptr_t)off); | ||
| 102 | - off += 8 * 32; | ||
| 103 | - qkv_tmp = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | ||
| 104 | - off += 8 * 32; | ||
| 105 | acc_buf = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | 122 | acc_buf = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); |
| 106 | off += 8 * 32; | 123 | off += 8 * 32; |
| 107 | calc_buf = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); | 124 | calc_buf = reinterpret_cast<__ubuf__ float *>((uintptr_t)off); |
| 108 | off += 8 * 32; | 125 | off += 8 * 32; |
| 109 | - out_buf = reinterpret_cast<__ubuf__ Dtype *>((uintptr_t)off); | 126 | + out_tile = reinterpret_cast<__ubuf__ Dtype *>((uintptr_t)off); |
| 110 | - off += 64 * sizeof(Dtype); | 127 | + off += kTile * kBlock * sizeof(Dtype); |
| 111 | if (off % 32 != 0) { | 128 | if (off % 32 != 0) { |
| 112 | off = (off + 31) / 32 * 32; | 129 | off = (off + 31) / 32 * 32; |
| 113 | } | 130 | } |
| @@ -116,26 +133,62 @@ public: | |||
| 116 | meta_lens = reinterpret_cast<__ubuf__ int32_t *>((uintptr_t)off); | 133 | meta_lens = reinterpret_cast<__ubuf__ int32_t *>((uintptr_t)off); |
| 117 | } | 134 | } |
| 118 | 135 | ||
| 119 | - __aicore__ inline float ReadFloat(__ubuf__ float *buf, int idx) | 136 | + __aicore__ inline void SetLaneMask(int w) |
| 137 | + { | ||
| 138 | + if (w >= kBlock) { | ||
| 139 | + set_vector_mask((uint64_t)-1, (uint64_t)-1); | ||
| 140 | + } else { | ||
| 141 | + SetMask(w); | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + // pipe_barrier(PIPE_X) alone does NOT order UB data produced/consumed by | ||
| 146 | + // *different* pipes (MTE2/V/MTE3/S); explicit event flags are required. | ||
| 147 | + // EVENT_ID0: MTE2 <-> V and V <-> MTE3 for the window/out_tile pipeline. | ||
| 148 | + // EVENT_ID1: scalar (S) pipe fences. | ||
| 149 | + // EVENT_ID2: stage_buf MTE2 <-> V inside LoadGmToFloat/StoreFloatToGm. | ||
| 150 | + __aicore__ inline void FlagMTE2V() | ||
| 151 | + { | ||
| 152 | + set_flag(PIPE_MTE2, PIPE_V, EVENT_ID0); | ||
| 153 | + wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID0); | ||
| 154 | + } | ||
| 155 | + __aicore__ inline void FlagVMTE2() | ||
| 156 | + { | ||
| 157 | + set_flag(PIPE_V, PIPE_MTE2, EVENT_ID0); | ||
| 158 | + wait_flag(PIPE_V, PIPE_MTE2, EVENT_ID0); | ||
| 159 | + } | ||
| 160 | + __aicore__ inline void FlagVMTE3() | ||
| 161 | + { | ||
| 162 | + set_flag(PIPE_V, PIPE_MTE3, EVENT_ID0); | ||
| 163 | + wait_flag(PIPE_V, PIPE_MTE3, EVENT_ID0); | ||
| 164 | + } | ||
| 165 | + __aicore__ inline void FlagMTE3V() | ||
| 166 | + { | ||
| 167 | + set_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 168 | + wait_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 169 | + } | ||
| 170 | + __aicore__ inline void FlagMTE2S() | ||
| 171 | + { | ||
| 172 | + set_flag(PIPE_MTE2, PIPE_S, EVENT_ID1); | ||
| 173 | + wait_flag(PIPE_MTE2, PIPE_S, EVENT_ID1); | ||
| 174 | + } | ||
| 175 | + __aicore__ inline void FlagVS() | ||
| 120 | { | 176 | { |
| 121 | set_flag(PIPE_V, PIPE_S, EVENT_ID1); | 177 | set_flag(PIPE_V, PIPE_S, EVENT_ID1); |
| 122 | wait_flag(PIPE_V, PIPE_S, EVENT_ID1); | 178 | wait_flag(PIPE_V, PIPE_S, EVENT_ID1); |
| 123 | - float val = buf[idx]; | ||
| 124 | - set_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 125 | - wait_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 126 | - return val; | ||
| 127 | } | 179 | } |
| 128 | - | 180 | + __aicore__ inline void FlagSV() |
| 129 | - __aicore__ inline void WriteFloat(__ubuf__ float *buf, int idx, float val) | ||
| 130 | { | 181 | { |
| 131 | - set_flag(PIPE_V, PIPE_S, EVENT_ID1); | ||
| 132 | - wait_flag(PIPE_V, PIPE_S, EVENT_ID1); | ||
| 133 | - buf[idx] = val; | ||
| 134 | set_flag(PIPE_S, PIPE_V, EVENT_ID1); | 182 | set_flag(PIPE_S, PIPE_V, EVENT_ID1); |
| 135 | wait_flag(PIPE_S, PIPE_V, EVENT_ID1); | 183 | wait_flag(PIPE_S, PIPE_V, EVENT_ID1); |
| 136 | } | 184 | } |
| 185 | + __aicore__ inline void FlagSMTE3() | ||
| 186 | + { | ||
| 187 | + set_flag(PIPE_S, PIPE_MTE3, EVENT_ID1); | ||
| 188 | + wait_flag(PIPE_S, PIPE_MTE3, EVENT_ID1); | ||
| 189 | + } | ||
| 137 | 190 | ||
| 138 | - __aicore__ inline void SiLU() | 191 | + __aicore__ inline void SiLU(__ubuf__ Dtype *dst) |
| 139 | { | 192 | { |
| 140 | vmuls(calc_buf, acc_buf, (float)-1.0, 1, 1, 1, 8, 8); | 193 | vmuls(calc_buf, acc_buf, (float)-1.0, 1, 1, 1, 8, 8); |
| 141 | pipe_barrier(PIPE_V); | 194 | pipe_barrier(PIPE_V); |
| @@ -144,21 +197,18 @@ public: | |||
| 144 | vadds(calc_buf, calc_buf, (float)1.0, 1, 1, 1, 8, 8); | 197 | vadds(calc_buf, calc_buf, (float)1.0, 1, 1, 1, 8, 8); |
| 145 | pipe_barrier(PIPE_V); | 198 | pipe_barrier(PIPE_V); |
| 146 | 199 | ||
| 147 | - wait_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 148 | if constexpr (std::is_same<Dtype, float>::value) { | 200 | if constexpr (std::is_same<Dtype, float>::value) { |
| 149 | - vdiv(out_buf, acc_buf, calc_buf, 1, 1, 1, 1, 8, 8, 8); | 201 | + vdiv(dst, acc_buf, calc_buf, 1, 1, 1, 1, 8, 8, 8); |
| 150 | } else { | 202 | } else { |
| 151 | vdiv(calc_buf, acc_buf, calc_buf, 1, 1, 1, 1, 8, 8, 8); | 203 | vdiv(calc_buf, acc_buf, calc_buf, 1, 1, 1, 1, 8, 8, 8); |
| 152 | pipe_barrier(PIPE_V); | 204 | pipe_barrier(PIPE_V); |
| 153 | if constexpr (std::is_same<Dtype, float16_t>::value) { | 205 | if constexpr (std::is_same<Dtype, float16_t>::value) { |
| 154 | - vconv_f322f16(out_buf, calc_buf, 1, 1, 1, 4, 8); | 206 | + vconv_f322f16(dst, calc_buf, 1, 1, 1, 4, 8); |
| 155 | } else { | 207 | } else { |
| 156 | - vconv_f322bf16r(out_buf, calc_buf, 1, 1, 1, 4, 8); | 208 | + vconv_f322bf16r(dst, calc_buf, 1, 1, 1, 4, 8); |
| 157 | } | 209 | } |
| 158 | } | 210 | } |
| 159 | pipe_barrier(PIPE_V); | 211 | pipe_barrier(PIPE_V); |
| 160 | - set_flag(PIPE_V, PIPE_MTE3, EVENT_ID0); | ||
| 161 | - wait_flag(PIPE_V, PIPE_MTE3, EVENT_ID0); | ||
| 162 | } | 212 | } |
| 163 | 213 | ||
| 164 | // Load nElem dtype values from GM into dstF (float). One round trip per | 214 | // Load nElem dtype values from GM into dstF (float). One round trip per |
| @@ -182,9 +232,6 @@ public: | |||
| 182 | pipe_barrier(PIPE_MTE2); | 232 | pipe_barrier(PIPE_MTE2); |
| 183 | set_flag(PIPE_MTE2, PIPE_V, EVENT_ID2); | 233 | set_flag(PIPE_MTE2, PIPE_V, EVENT_ID2); |
| 184 | wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID2); | 234 | wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID2); |
| 185 | - | ||
| 186 | - // The 128-bit vector mask cycles across repeats (64 lanes each); | ||
| 187 | - // SetMask only covers up to two repeats, so use a full mask above. | ||
| 188 | if (take >= 2 * VECTOR_MAX_NUM_OF_FP32) { | 235 | if (take >= 2 * VECTOR_MAX_NUM_OF_FP32) { |
| 189 | set_vector_mask((uint64_t)-1, (uint64_t)-1); | 236 | set_vector_mask((uint64_t)-1, (uint64_t)-1); |
| 190 | } else { | 237 | } else { |
| @@ -197,6 +244,8 @@ public: | |||
| 197 | vconv_bf162f32(dstF + done, stage_buf, repeat, 1, 1, 8, 4); | 244 | vconv_bf162f32(dstF + done, stage_buf, repeat, 1, 1, 8, 4); |
| 198 | } | 245 | } |
| 199 | pipe_barrier(PIPE_V); | 246 | pipe_barrier(PIPE_V); |
| 247 | + set_flag(PIPE_V, PIPE_MTE2, EVENT_ID2); | ||
| 248 | + wait_flag(PIPE_V, PIPE_MTE2, EVENT_ID2); | ||
| 200 | done += take; | 249 | done += take; |
| 201 | } | 250 | } |
| 202 | set_vector_mask((uint64_t)-1, (uint64_t)-1); | 251 | set_vector_mask((uint64_t)-1, (uint64_t)-1); |
| @@ -242,6 +291,216 @@ public: | |||
| 242 | set_vector_mask((uint64_t)-1, (uint64_t)-1); | 291 | set_vector_mask((uint64_t)-1, (uint64_t)-1); |
| 243 | } | 292 | } |
| 244 | 293 | ||
| 294 | + // Load a [nPos, w] tile of channel-block c0 (w channels starting at c0) from | ||
| 295 | + // GM positions [tokenBase+loPos, tokenBase+loPos+nPos) into UB at row dstIdx. | ||
| 296 | + // Each UB row is kBlock elements wide. When both the row and the channel | ||
| 297 | + // width are 32B-aligned, use one multi-burst DMA; otherwise fall back to a | ||
| 298 | + // per-position copy. | ||
| 299 | + __aicore__ inline void LoadTileInto(__ubuf__ Dtype *dst, int tokenBase, int c0, int loPos, | ||
| 300 | + int nPos, int dstIdx) | ||
| 301 | + { | ||
| 302 | + int C = (int)channels; | ||
| 303 | + int w = MIN(kBlock, C - c0); | ||
| 304 | + __gm__ Dtype *src = input + ((tokenBase + loPos) * C + c0); | ||
| 305 | + int burstBytes = w * (int)sizeof(Dtype); | ||
| 306 | + int burstBlocks = burstBytes / BLOCK_SIZE; | ||
| 307 | + int rowBytes = C * (int)sizeof(Dtype); | ||
| 308 | + if (w == kBlock && burstBlocks * BLOCK_SIZE == burstBytes && rowBytes % BLOCK_SIZE == 0) { | ||
| 309 | + uint64_t cfg = | ||
| 310 | + __set_dmi_config(0, nPos, burstBlocks, rowBytes / BLOCK_SIZE - burstBlocks, 0); | ||
| 311 | + copy_gm_to_ubuf(dst + dstIdx * kBlock, src, cfg); | ||
| 312 | + } else { | ||
| 313 | + for (int i = 0; i < nPos; i++) { | ||
| 314 | + CopyGmToUbufAligned(dst + (dstIdx + i) * kBlock, src + i * C, | ||
| 315 | + static_cast<uint32_t>(burstBytes)); | ||
| 316 | + } | ||
| 317 | + } | ||
| 318 | + pipe_barrier(PIPE_MTE2); | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + __aicore__ inline void ConvertTile(int nPos, int dstIdx) | ||
| 322 | + { | ||
| 323 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 324 | + return; | ||
| 325 | + } | ||
| 326 | + int off = dstIdx * kBlock; | ||
| 327 | + set_vector_mask((uint64_t)-1, (uint64_t)-1); | ||
| 328 | + if constexpr (std::is_same<Dtype, float16_t>::value) { | ||
| 329 | + vconv_f162f32(window_f + off, win_raw + off, nPos, 1, 1, 8, 4); | ||
| 330 | + } else { | ||
| 331 | + vconv_bf162f32(window_f + off, win_raw + off, nPos, 1, 1, 8, 4); | ||
| 332 | + } | ||
| 333 | + pipe_barrier(PIPE_V); | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + // Load [w, K] weights and re-layout them to [K, kBlock] so tap j lives at a | ||
| 337 | + // contiguous 64-lane vector (w_reorg + j*kBlock). | ||
| 338 | + __aicore__ inline void LoadWeights(int c0, int w) | ||
| 339 | + { | ||
| 340 | + int K = (int)kernelDim; | ||
| 341 | + LoadGmToFloat(weight + c0 * K, w * K, w_f); | ||
| 342 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 343 | + FlagMTE2S(); | ||
| 344 | + } else { | ||
| 345 | + FlagVS(); | ||
| 346 | + } | ||
| 347 | + for (int l = 0; l < w; l++) { | ||
| 348 | + for (int j = 0; j < K; j++) { | ||
| 349 | + w_reorg[j * kBlock + l] = w_f[l * K + j]; | ||
| 350 | + } | ||
| 351 | + } | ||
| 352 | + FlagSV(); | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + // Load [w, K] state and re-layout it to [K, kBlock]. | ||
| 356 | + __aicore__ inline void LoadState(int b, int c0, int w) | ||
| 357 | + { | ||
| 358 | + int K = (int)kernelDim; | ||
| 359 | + int stateBase = (b * (int)channels + c0) * K; | ||
| 360 | + LoadGmToFloat(state + stateBase, w * K, state_f); | ||
| 361 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 362 | + FlagMTE2S(); | ||
| 363 | + } else { | ||
| 364 | + FlagVS(); | ||
| 365 | + } | ||
| 366 | + for (int l = 0; l < w; l++) { | ||
| 367 | + for (int p = 0; p < K; p++) { | ||
| 368 | + state_reorg[p * kBlock + l] = state_f[l * K + p]; | ||
| 369 | + } | ||
| 370 | + } | ||
| 371 | + FlagSV(); | ||
| 372 | + } | ||
| 373 | + | ||
| 374 | + // Compute one output position (channel block c0, w lanes) and store the | ||
| 375 | + // SiLU result into out_tile row (s - s0). | ||
| 376 | + __aicore__ inline void ComputePosition(int s, int s0, int w) | ||
| 377 | + { | ||
| 378 | + int K = (int)kernelDim; | ||
| 379 | + SetLaneMask(w); | ||
| 380 | + vector_dup(acc_buf, 0.0f, 1, 1, 1, 8, 8); | ||
| 381 | + pipe_barrier(PIPE_V); | ||
| 382 | + for (int j = 0; j < K; j++) { | ||
| 383 | + int p = s + 1 + j; | ||
| 384 | + __ubuf__ float *tap = | ||
| 385 | + (p < K) ? (state_reorg + p * kBlock) : (window_f + (p - s0 - 1) * kBlock); | ||
| 386 | + vmul(calc_buf, tap, w_reorg + j * kBlock, 1, 1, 1, 1, 8, 8, 8); | ||
| 387 | + pipe_barrier(PIPE_V); | ||
| 388 | + vadd(acc_buf, acc_buf, calc_buf, 1, 1, 1, 1, 8, 8, 8); | ||
| 389 | + pipe_barrier(PIPE_V); | ||
| 390 | + } | ||
| 391 | + SiLU(out_tile + (s - s0) * kBlock); | ||
| 392 | + } | ||
| 393 | + | ||
| 394 | + // Store out_tile rows [0, nPos) to GM positions [tokenBase+s0, tokenBase+s0+nPos) | ||
| 395 | + // with a channel-stride C. Bulk multi-burst DMA when the full block is used. | ||
| 396 | + __aicore__ inline void StoreTile(int tokenBase, int c0, int s0, int nPos, int w) | ||
| 397 | + { | ||
| 398 | + int C = (int)channels; | ||
| 399 | + __gm__ Dtype *dst = output + (tokenBase + s0) * C + c0; | ||
| 400 | + int burstBytes = w * (int)sizeof(Dtype); | ||
| 401 | + int burstBlocks = burstBytes / BLOCK_SIZE; | ||
| 402 | + int rowBlocks = C * (int)sizeof(Dtype) / BLOCK_SIZE; | ||
| 403 | + if (w == kBlock && burstBlocks * BLOCK_SIZE == burstBytes && | ||
| 404 | + rowBlocks * BLOCK_SIZE == C * (int)sizeof(Dtype)) { | ||
| 405 | + uint64_t cfg = __set_dmi_config(0, nPos, burstBlocks, 0, rowBlocks - burstBlocks); | ||
| 406 | + copy_ubuf_to_gm(dst, out_tile, cfg); | ||
| 407 | + } else { | ||
| 408 | + for (int s = 0; s < nPos; s++) { | ||
| 409 | + CopyUbufToGmAligned(dst + s * C, out_tile + s * kBlock, | ||
| 410 | + static_cast<uint32_t>(burstBytes)); | ||
| 411 | + } | ||
| 412 | + } | ||
| 413 | + pipe_barrier(PIPE_MTE3); | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + __aicore__ inline void ProcessSequence(int tokenBase, int c0, int w, int S) | ||
| 417 | + { | ||
| 418 | + int K = (int)kernelDim; | ||
| 419 | + for (int s0 = 0; s0 < S; s0 += kTile) { | ||
| 420 | + int loPos = (s0 == 0) ? 0 : (s0 - K + 1); | ||
| 421 | + int hiPos = MIN(s0 + kTile - 1, S - 1); | ||
| 422 | + if (hiPos < loPos) { | ||
| 423 | + break; | ||
| 424 | + } | ||
| 425 | + int nPos = hiPos - loPos + 1; | ||
| 426 | + int dstIdx = (s0 == 0) ? (K - 1) : 0; | ||
| 427 | + | ||
| 428 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 429 | + LoadTileInto(window_f, tokenBase, c0, loPos, nPos, dstIdx); | ||
| 430 | + } else { | ||
| 431 | + LoadTileInto(win_raw, tokenBase, c0, loPos, nPos, dstIdx); | ||
| 432 | + } | ||
| 433 | + FlagMTE2V(); | ||
| 434 | + ConvertTile(nPos, dstIdx); | ||
| 435 | + FlagVMTE2(); | ||
| 436 | + | ||
| 437 | + int sEnd = MIN(s0 + kTile, S); | ||
| 438 | + for (int s = s0; s < sEnd; s++) { | ||
| 439 | + ComputePosition(s, s0, w); | ||
| 440 | + } | ||
| 441 | + FlagVMTE3(); | ||
| 442 | + StoreTile(tokenBase, c0, s0, sEnd - s0, w); | ||
| 443 | + FlagMTE3V(); | ||
| 444 | + } | ||
| 445 | + } | ||
| 446 | + | ||
| 447 | + // Load the trailing input window used to derive the new state. | ||
| 448 | + __aicore__ inline void LoadStateWin(int tokenBase, int c0, int S) | ||
| 449 | + { | ||
| 450 | + int K = (int)kernelDim; | ||
| 451 | + int firstPos = MAX(0, S - K); | ||
| 452 | + int nPos = S - firstPos; | ||
| 453 | + if (nPos <= 0) { | ||
| 454 | + return; | ||
| 455 | + } | ||
| 456 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 457 | + LoadTileInto(state_win, tokenBase, c0, firstPos, nPos, 0); | ||
| 458 | + } else { | ||
| 459 | + LoadTileInto(win_raw, tokenBase, c0, firstPos, nPos, 0); | ||
| 460 | + FlagMTE2V(); | ||
| 461 | + set_vector_mask((uint64_t)-1, (uint64_t)-1); | ||
| 462 | + if constexpr (std::is_same<Dtype, float16_t>::value) { | ||
| 463 | + vconv_f162f32(state_win, win_raw, nPos, 1, 1, 8, 4); | ||
| 464 | + } else { | ||
| 465 | + vconv_bf162f32(state_win, win_raw, nPos, 1, 1, 8, 4); | ||
| 466 | + } | ||
| 467 | + pipe_barrier(PIPE_V); | ||
| 468 | + FlagVMTE2(); | ||
| 469 | + } | ||
| 470 | + } | ||
| 471 | + | ||
| 472 | + __aicore__ inline void WriteBackState(int b, int tokenBase, int c0, int w, int S) | ||
| 473 | + { | ||
| 474 | + int K = (int)kernelDim; | ||
| 475 | + int C = (int)channels; | ||
| 476 | + int firstPos = MAX(0, S - K); | ||
| 477 | + | ||
| 478 | + LoadStateWin(tokenBase, c0, S); | ||
| 479 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 480 | + FlagMTE2S(); | ||
| 481 | + } else { | ||
| 482 | + FlagVS(); | ||
| 483 | + } | ||
| 484 | + for (int t = 0; t < K; t++) { | ||
| 485 | + int concatIdx = S + t; | ||
| 486 | + for (int l = 0; l < w; l++) { | ||
| 487 | + float v; | ||
| 488 | + if (concatIdx < K) { | ||
| 489 | + v = state_reorg[concatIdx * kBlock + l]; | ||
| 490 | + } else { | ||
| 491 | + v = state_win[(concatIdx - K - firstPos) * kBlock + l]; | ||
| 492 | + } | ||
| 493 | + new_state_f[l * K + t] = v; | ||
| 494 | + } | ||
| 495 | + } | ||
| 496 | + if constexpr (std::is_same<Dtype, float>::value) { | ||
| 497 | + FlagSMTE3(); | ||
| 498 | + } else { | ||
| 499 | + FlagSV(); | ||
| 500 | + } | ||
| 501 | + StoreFloatToGm(new_state_f, state + (b * C + c0) * K, w * K); | ||
| 502 | + } | ||
| 503 | + | ||
| 245 | __aicore__ inline bool Packed() | 504 | __aicore__ inline bool Packed() |
| 246 | { | 505 | { |
| 247 | // Host sets seqLen=0 for packed mixed-length. GM pointer != nullptr is | 506 | // Host sets seqLen=0 for packed mixed-length. GM pointer != nullptr is |
| @@ -249,98 +508,6 @@ public: | |||
| 249 | return seqLen == 0; | 508 | return seqLen == 0; |
| 250 | } | 509 | } |
| 251 | 510 | ||
| 252 | - // Packed [T,C]: tokens of one channel are stride-C apart. | ||
| 253 | - // align_b16 bursts are placed 32B apart in UB; compact the first Dtype of | ||
| 254 | - // each slot, then convert. Never DMA from a non-32B UB pointer (ADDR_MISALIGN). | ||
| 255 | - __aicore__ inline void LoadPackedChannel(__gm__ Dtype *base, int S, int stride, | ||
| 256 | - __ubuf__ float *dstF) | ||
| 257 | - { | ||
| 258 | - constexpr int kSlot = BLOCK_SIZE / sizeof(Dtype); | ||
| 259 | - constexpr int kMaxTake = kMaxInputF / kSlot; | ||
| 260 | - uint32_t elemBytes = static_cast<uint32_t>(sizeof(Dtype)); | ||
| 261 | - uint32_t srcGap = static_cast<uint32_t>(stride - 1) * elemBytes; | ||
| 262 | - int done = 0; | ||
| 263 | - while (done < S) { | ||
| 264 | - int take = S - done; | ||
| 265 | - if (take > kMaxTake) { | ||
| 266 | - take = kMaxTake; | ||
| 267 | - } | ||
| 268 | - if constexpr (std::is_same<Dtype, float>::value) { | ||
| 269 | - copy_gm_to_ubuf_align_b32(stage_buf, base + done * stride, 0, (uint16_t)take, | ||
| 270 | - elemBytes, 0, 0, srcGap, 0); | ||
| 271 | - } else { | ||
| 272 | - copy_gm_to_ubuf_align_b16(stage_buf, base + done * stride, 0, (uint16_t)take, | ||
| 273 | - elemBytes, 0, 0, srcGap, 0); | ||
| 274 | - } | ||
| 275 | - pipe_barrier(PIPE_MTE2); | ||
| 276 | - set_flag(PIPE_MTE2, PIPE_S, EVENT_ID2); | ||
| 277 | - wait_flag(PIPE_MTE2, PIPE_S, EVENT_ID2); | ||
| 278 | - for (int i = 0; i < take; ++i) { | ||
| 279 | - stage_buf[i] = stage_buf[i * kSlot]; | ||
| 280 | - } | ||
| 281 | - set_flag(PIPE_S, PIPE_V, EVENT_ID2); | ||
| 282 | - wait_flag(PIPE_S, PIPE_V, EVENT_ID2); | ||
| 283 | - if constexpr (std::is_same<Dtype, float>::value) { | ||
| 284 | - for (int i = 0; i < take; ++i) { | ||
| 285 | - WriteFloat(dstF, done + i, ReadFloat(stage_buf, i)); | ||
| 286 | - } | ||
| 287 | - } else { | ||
| 288 | - if (take >= 2 * VECTOR_MAX_NUM_OF_FP32) { | ||
| 289 | - set_vector_mask((uint64_t)-1, (uint64_t)-1); | ||
| 290 | - } else { | ||
| 291 | - SetMask(take); | ||
| 292 | - } | ||
| 293 | - int repeat = DIV_ROUND_UP(take, VECTOR_MAX_NUM_OF_FP32); | ||
| 294 | - if constexpr (std::is_same<Dtype, float16_t>::value) { | ||
| 295 | - vconv_f162f32(dstF + done, stage_buf, repeat, 1, 1, 8, 4); | ||
| 296 | - } else { | ||
| 297 | - vconv_bf162f32(dstF + done, stage_buf, repeat, 1, 1, 8, 4); | ||
| 298 | - } | ||
| 299 | - pipe_barrier(PIPE_V); | ||
| 300 | - set_vector_mask((uint64_t)-1, (uint64_t)-1); | ||
| 301 | - } | ||
| 302 | - done += take; | ||
| 303 | - } | ||
| 304 | - } | ||
| 305 | - | ||
| 306 | - __aicore__ inline void StorePackedChannel(__gm__ Dtype *base, int S, int stride, | ||
| 307 | - __ubuf__ Dtype *src, int nElem) | ||
| 308 | - { | ||
| 309 | - (void)S; | ||
| 310 | - uint32_t elemBytes = static_cast<uint32_t>(sizeof(Dtype)); | ||
| 311 | - // Bulk scatter: expand contiguous src[0..nElem) into the 32B-slot layout | ||
| 312 | - // that align_b16 expects on the UB side (each burst lives in its own | ||
| 313 | - // 32B slot), then one copy_ubuf_to_gm_align_b16 writes every burst to a | ||
| 314 | - // GM position stride elements apart (dstGap). nElem <= kBlock=64, so | ||
| 315 | - // slot staging fits stage_buf (8KB for fp16/bf16). | ||
| 316 | - constexpr int kSlot = BLOCK_SIZE / sizeof(Dtype); | ||
| 317 | - // SiLU (vdiv) writes out_buf on V pipe | ||
| 318 | - set_flag(PIPE_V, PIPE_S, EVENT_ID0); | ||
| 319 | - wait_flag(PIPE_V, PIPE_S, EVENT_ID0); | ||
| 320 | - for (int s = 0; s < nElem; ++s) { | ||
| 321 | - stage_buf[s * kSlot] = src[s]; | ||
| 322 | - } | ||
| 323 | - // S→MTE3: slot scatter must be visible before align_b16 DMA reads stage_buf. | ||
| 324 | - set_flag(PIPE_S, PIPE_MTE3, EVENT_ID0); | ||
| 325 | - wait_flag(PIPE_S, PIPE_MTE3, EVENT_ID0); | ||
| 326 | - uint32_t dstGap = static_cast<uint32_t>(stride - 1) * elemBytes; | ||
| 327 | - copy_ubuf_to_gm_align_b16(base, stage_buf, 0, static_cast<uint16_t>(nElem), elemBytes, 0, 0, | ||
| 328 | - 0, dstGap); | ||
| 329 | - pipe_barrier(PIPE_MTE3); | ||
| 330 | - } | ||
| 331 | - | ||
| 332 | - __aicore__ inline void WriteBackState(int batchIdx, int channel, int S) | ||
| 333 | - { | ||
| 334 | - int K = (int)kernelDim; | ||
| 335 | - for (int j = 0; j < K; ++j) { | ||
| 336 | - int absIdx = S + j; | ||
| 337 | - float v = (absIdx < K) ? ReadFloat(state_f, absIdx) : ReadFloat(input_f, absIdx - K); | ||
| 338 | - WriteFloat(new_state_f, j, v); | ||
| 339 | - } | ||
| 340 | - int stateBase = (batchIdx * (int)channels + channel) * K; | ||
| 341 | - StoreFloatToGm(new_state_f, state + stateBase, K); | ||
| 342 | - } | ||
| 343 | - | ||
| 344 | __aicore__ inline void Process() | 511 | __aicore__ inline void Process() |
| 345 | { | 512 | { |
| 346 | int K = (int)kernelDim; | 513 | int K = (int)kernelDim; |
| @@ -357,165 +524,39 @@ public: | |||
| 357 | pipe_barrier(PIPE_MTE2); | 524 | pipe_barrier(PIPE_MTE2); |
| 358 | CopyGmToUbufAligned(meta_lens, queryLens, n * sizeof(int32_t)); | 525 | CopyGmToUbufAligned(meta_lens, queryLens, n * sizeof(int32_t)); |
| 359 | pipe_barrier(PIPE_MTE2); | 526 | pipe_barrier(PIPE_MTE2); |
| 360 | - set_flag(PIPE_MTE2, PIPE_S, EVENT_ID1); | 527 | + FlagMTE2S(); |
| 361 | - wait_flag(PIPE_MTE2, PIPE_S, EVENT_ID1); | ||
| 362 | } | 528 | } |
| 363 | 529 | ||
| 364 | - // Byte offsets for vgather: lane p reads base + off_ramp[p]. | 530 | + int C = (int)channels; |
| 365 | - for (int k = 0; k < kBlock; k++) { | 531 | + int nBlocks = DIV_ROUND_UP(C, kBlock); |
| 366 | - off_ramp[k] = static_cast<uint32_t>(k * (int)sizeof(float)); | 532 | + for (int cb = 0; cb < nBlocks; cb++) { |
| 367 | - } | 533 | + if (cb % GetBlockNum() != GetBlockIdx()) { |
| 368 | - set_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 369 | - wait_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 370 | - | ||
| 371 | - for (int channel = 0; channel < (int)channels; channel++) { | ||
| 372 | - if (channel % GetBlockNum() != GetBlockIdx()) | ||
| 373 | continue; | 534 | continue; |
| 374 | - | ||
| 375 | - int wOffset = channel * K; | ||
| 376 | - if constexpr (std::is_same<Dtype, float>::value) { | ||
| 377 | - CopyGmToUbufAligned(kernel_buf, weight + wOffset, | ||
| 378 | - static_cast<uint32_t>(K * sizeof(Dtype))); | ||
| 379 | - pipe_barrier(PIPE_MTE2); | ||
| 380 | - } else { | ||
| 381 | - CopyGmToUbufAligned(tmp_kernel_buf, weight + wOffset, | ||
| 382 | - static_cast<uint32_t>(K * sizeof(Dtype))); | ||
| 383 | - pipe_barrier(PIPE_MTE2); | ||
| 384 | - set_flag(PIPE_MTE2, PIPE_V, EVENT_ID0); | ||
| 385 | - wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID0); | ||
| 386 | - uint64_t wmask = (K >= 64) ? (uint64_t)-1 : ((1ull << K) - 1ull); | ||
| 387 | - set_vector_mask(0, wmask); | ||
| 388 | - if constexpr (std::is_same<Dtype, float16_t>::value) { | ||
| 389 | - vconv_f162f32(kernel_buf, tmp_kernel_buf, 1, 1, 1, 8, 4); | ||
| 390 | - } else { | ||
| 391 | - vconv_bf162f32(kernel_buf, tmp_kernel_buf, 1, 1, 1, 8, 4); | ||
| 392 | - } | ||
| 393 | - pipe_barrier(PIPE_V); | ||
| 394 | - set_vector_mask((uint64_t)-1, (uint64_t)-1); | ||
| 395 | } | 535 | } |
| 536 | + int c0 = cb * kBlock; | ||
| 537 | + int w = MIN(kBlock, C - c0); | ||
| 396 | 538 | ||
| 397 | - // Read the K fp32 weights into scalar registers (once per channel). | 539 | + LoadWeights(c0, w); |
| 398 | - set_flag(PIPE_V, PIPE_S, EVENT_ID1); | 540 | + for (int b = 0; b < (int)batch; b++) { |
| 399 | - wait_flag(PIPE_V, PIPE_S, EVENT_ID1); | ||
| 400 | - float w[kMaxKernel]; | ||
| 401 | - for (int j = 0; j < K; ++j) { | ||
| 402 | - w[j] = kernel_buf[j]; | ||
| 403 | - } | ||
| 404 | - set_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 405 | - wait_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 406 | - | ||
| 407 | - set_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 408 | - for (int batchIdx = 0; batchIdx < (int)batch; ++batchIdx) { | ||
| 409 | int S; | 541 | int S; |
| 410 | - int start; | 542 | + int tokenBase; |
| 411 | if (packed) { | 543 | if (packed) { |
| 412 | - // meta_* live in UB; must not read them from V without a drain. | 544 | + S = (int)meta_lens[b]; |
| 413 | - pipe_barrier(PIPE_ALL); | 545 | + tokenBase = (int)meta_start[b]; |
| 414 | - S = (int)meta_lens[batchIdx]; | ||
| 415 | - start = (int)meta_start[batchIdx]; | ||
| 416 | } else { | 546 | } else { |
| 417 | S = (int)seqLen; | 547 | S = (int)seqLen; |
| 418 | - start = 0; | 548 | + tokenBase = b * S; |
| 419 | } | 549 | } |
| 420 | if (S <= 0 || S > kMaxInputF) { | 550 | if (S <= 0 || S > kMaxInputF) { |
| 421 | continue; | 551 | continue; |
| 422 | } | 552 | } |
| 423 | - int stateBase = (batchIdx * (int)channels + channel) * K; | 553 | + LoadState(b, c0, w); |
| 424 | - LoadGmToFloat(state + stateBase, K, state_f); | 554 | + ProcessSequence(tokenBase, c0, w, S); |
| 425 | - if (packed) { | ||
| 426 | - LoadPackedChannel(input + start * (int)channels + channel, S, (int)channels, | ||
| 427 | - input_f); | ||
| 428 | - } else { | ||
| 429 | - int inputBase = (batchIdx * (int)channels + channel) * S; | ||
| 430 | - LoadGmToFloat(input + inputBase, S, input_f); | ||
| 431 | - } | ||
| 432 | - | ||
| 433 | - // ---- Straddle outputs [0, K-1): window spans state and input ---- | ||
| 434 | - int scalarEnd = MIN(K - 1, S); | ||
| 435 | - if (scalarEnd > 0) { | ||
| 436 | - float st[kMaxKernel]; | ||
| 437 | - float in[kMaxKernel]; | ||
| 438 | - set_flag(PIPE_MTE2, PIPE_S, EVENT_ID1); | ||
| 439 | - wait_flag(PIPE_MTE2, PIPE_S, EVENT_ID1); | ||
| 440 | - set_flag(PIPE_V, PIPE_S, EVENT_ID1); | ||
| 441 | - wait_flag(PIPE_V, PIPE_S, EVENT_ID1); | ||
| 442 | - for (int j = 0; j < K; ++j) { | ||
| 443 | - st[j] = state_f[j]; | ||
| 444 | - } | ||
| 445 | - for (int j = 0; j < MIN(K, S); ++j) { | ||
| 446 | - in[j] = input_f[j]; | ||
| 447 | - } | ||
| 448 | - set_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 449 | - wait_flag(PIPE_S, PIPE_V, EVENT_ID1); | ||
| 450 | - for (int pos = 0; pos < scalarEnd; ++pos) { | ||
| 451 | - float dot = 0.0f; | ||
| 452 | - for (int j = 0; j < K; ++j) { | ||
| 453 | - int t = pos + 1 + j; | ||
| 454 | - float v = (t < K) ? st[t] : in[t - K]; | ||
| 455 | - dot += w[j] * v; | ||
| 456 | - } | ||
| 457 | - WriteFloat(acc_buf, pos, dot); | ||
| 458 | - } | ||
| 459 | - SiLU(); | ||
| 460 | - set_flag(PIPE_S, PIPE_MTE3, EVENT_ID0); | ||
| 461 | - wait_flag(PIPE_S, PIPE_MTE3, EVENT_ID0); | ||
| 462 | - if (packed) { | ||
| 463 | - StorePackedChannel(output + start * (int)channels + channel, S, | ||
| 464 | - (int)channels, out_buf, scalarEnd); | ||
| 465 | - } else { | ||
| 466 | - int outOffset = (batchIdx * (int)channels + channel) * S; | ||
| 467 | - CopyUbufToGmAligned(output + outOffset, out_buf, scalarEnd * sizeof(Dtype)); | ||
| 468 | - pipe_barrier(PIPE_MTE3); | ||
| 469 | - } | ||
| 470 | - set_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 471 | - } | ||
| 472 | - | ||
| 473 | - // ---- Windows fully inside input: full-width 64-lane blocks ---- | ||
| 474 | - for (int i = K - 1; i < S; i += kBlock) { | ||
| 475 | - int realLen = MIN(kBlock, S - i); | ||
| 476 | - | ||
| 477 | - vector_dup(acc_buf, 0.0f, 1, 1, 1, 8, 8); | ||
| 478 | - pipe_barrier(PIPE_V); | ||
| 479 | - for (int j = 0; j < K; ++j) { | ||
| 480 | - uint32_t baseAddr = static_cast<uint32_t>( | ||
| 481 | - (uint64_t)input_f + (i + 1 - K + j) * (int)sizeof(float)); | ||
| 482 | - vgather((__ubuf__ uint32_t *)qkv_tmp, off_ramp, baseAddr, 8, 1); | ||
| 483 | - // vgather UB writeback is NOT covered by pipe_barrier(PIPE_V) | ||
| 484 | - // (proven on this NPU). Use an S-roundtrip event fence before | ||
| 485 | - // vmuls consumes qkv_tmp, otherwise the dependent vector op can | ||
| 486 | - // be issued while the gather is still writing -> AIV hazard. | ||
| 487 | - set_flag(PIPE_V, PIPE_S, EVENT_ID3); | ||
| 488 | - wait_flag(PIPE_V, PIPE_S, EVENT_ID3); | ||
| 489 | - set_flag(PIPE_S, PIPE_V, EVENT_ID3); | ||
| 490 | - wait_flag(PIPE_S, PIPE_V, EVENT_ID3); | ||
| 491 | - vmuls(calc_buf, qkv_tmp, w[j], 1, 1, 1, 8, 8); | ||
| 492 | - pipe_barrier(PIPE_V); | ||
| 493 | - vadd(acc_buf, acc_buf, calc_buf, 1, 1, 1, 1, 8, 8, 8); | ||
| 494 | - pipe_barrier(PIPE_V); | ||
| 495 | - } | ||
| 496 | - SiLU(); | ||
| 497 | - | ||
| 498 | - set_flag(PIPE_S, PIPE_MTE3, EVENT_ID0); | ||
| 499 | - wait_flag(PIPE_S, PIPE_MTE3, EVENT_ID0); | ||
| 500 | - if (packed) { | ||
| 501 | - StorePackedChannel(output + (start + i) * (int)channels + channel, S, | ||
| 502 | - (int)channels, out_buf, realLen); | ||
| 503 | - } else { | ||
| 504 | - int outOffset = (batchIdx * (int)channels + channel) * S + i; | ||
| 505 | - CopyUbufToGmAligned(output + outOffset, out_buf, realLen * sizeof(Dtype)); | ||
| 506 | - pipe_barrier(PIPE_MTE3); | ||
| 507 | - } | ||
| 508 | - set_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 509 | - } | ||
| 510 | - | ||
| 511 | if (updateState) { | 555 | if (updateState) { |
| 512 | - wait_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | 556 | + WriteBackState(b, tokenBase, c0, w, S); |
| 513 | - WriteBackState(batchIdx, channel, S); | ||
| 514 | - set_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 515 | } | 557 | } |
| 516 | pipe_barrier(PIPE_ALL); | 558 | pipe_barrier(PIPE_ALL); |
| 517 | } | 559 | } |
| 518 | - wait_flag(PIPE_MTE3, PIPE_V, EVENT_ID0); | ||
| 519 | } | 560 | } |
| 520 | } | 561 | } |
| 521 | 562 | ||
| @@ -528,16 +569,17 @@ private: | |||
| 528 | __gm__ int32_t *queryLens; | 569 | __gm__ int32_t *queryLens; |
| 529 | 570 | ||
| 530 | __ubuf__ Dtype *stage_buf; | 571 | __ubuf__ Dtype *stage_buf; |
| 531 | - __ubuf__ Dtype *tmp_kernel_buf; | 572 | + __ubuf__ float *w_f; |
| 532 | - __ubuf__ float *kernel_buf; | 573 | + __ubuf__ float *w_reorg; |
| 533 | __ubuf__ float *state_f; | 574 | __ubuf__ float *state_f; |
| 534 | - __ubuf__ float *input_f; | 575 | + __ubuf__ float *state_reorg; |
| 576 | + __ubuf__ Dtype *win_raw; | ||
| 577 | + __ubuf__ float *window_f; | ||
| 578 | + __ubuf__ float *state_win; | ||
| 535 | __ubuf__ float *new_state_f; | 579 | __ubuf__ float *new_state_f; |
| 536 | - __ubuf__ uint32_t *off_ramp; | ||
| 537 | - __ubuf__ float *qkv_tmp; | ||
| 538 | __ubuf__ float *acc_buf; | 580 | __ubuf__ float *acc_buf; |
| 539 | __ubuf__ float *calc_buf; | 581 | __ubuf__ float *calc_buf; |
| 540 | - __ubuf__ Dtype *out_buf; | 582 | + __ubuf__ Dtype *out_tile; |
| 541 | __ubuf__ int32_t *meta_start; | 583 | __ubuf__ int32_t *meta_start; |
| 542 | __ubuf__ int32_t *meta_lens; | 584 | __ubuf__ int32_t *meta_lens; |
| 543 | 585 | ||
| @@ -801,20 +801,15 @@ void XModel::ForwardAttnLinear(XRuntime &rt, uint32_t layer, | |||
| 801 | } | 801 | } |
| 802 | XTensor convStateBatch; | 802 | XTensor convStateBatch; |
| 803 | convStateBatch.Init({batch, convDim, _c.linearConvKernelDim}, convState.dtype, convState.ptr); | 803 | convStateBatch.Init({batch, convDim, _c.linearConvKernelDim}, convState.dtype, convState.ptr); |
| 804 | - // Decode (seqlen==1) and small tasks use the packed 2D path: | 804 | + // Decode (seqlen==1) uses the packed 2D path: [B,1,C] and [B,C,1] are |
| 805 | - // mixQkv [m, qkvDim] token-major (matmul output) | 805 | + // memory-identical for contiguous tensors, so the two Transpose_1_2 are |
| 806 | - // convPacked [m, convDim] token-major (Step4 SplitCol consumes directly) | 806 | + // identity ops. Skip them and use the packed 2D path. |
| 807 | - // queryStartLoc/lens are populated every step by PrepareAttn. | ||
| 808 | - // | ||
| 809 | - // decode: seqlen==1 (any batch). [B,1,C] and [B,C,1] are memory-identical | ||
| 810 | - // for contiguous tensors, so the two Transpose_1_2 are identity ops even | ||
| 811 | - // with multiple requests in flight. Skip them and use the packed 2D path. | ||
| 812 | // | 807 | // |
| 813 | // Prefill (seqlen>1, uniform) uses the token-row-parallel kernel when its | 808 | // Prefill (seqlen>1, uniform) uses the token-row-parallel kernel when its |
| 814 | - // constraints hold (K in {1,2,4}, seqlen >= K, convDim % 1024 == 0): it | 809 | + // constraints hold (K in {1,2,4}, seqlen >= K, convDim % 1024 == 0). |
| 815 | - // consumes token-major [m,C] mixQkv directly, removing the two | 810 | + // Otherwise the fused conv kernel consumes/produces the token-major |
| 816 | - // XliteOpTranspose_1_2 of the old 3D path. | 811 | + // [B,S,C] layout directly, so no Transpose passes are needed either. |
| 817 | - // Constraints unmet -> old 3D transpose path. Decode/mixed -> packed. | 812 | + // Decode/mixed -> packed. |
| 818 | bool decodeStep = (seqlen == 1); | 813 | bool decodeStep = (seqlen == 1); |
| 819 | uint32_t convK = _c.linearConvKernelDim; | 814 | uint32_t convK = _c.linearConvKernelDim; |
| 820 | bool useToken = uniform && !decodeStep && seqlen >= convK && | 815 | bool useToken = uniform && !decodeStep && seqlen >= convK && |
| @@ -824,18 +819,12 @@ void XModel::ForwardAttnLinear(XRuntime &rt, uint32_t layer, | |||
| 824 | seqlen, /*updateState=*/true); | 819 | seqlen, /*updateState=*/true); |
| 825 | rt.PutTensor(mixQkv); | 820 | rt.PutTensor(mixQkv); |
| 826 | } else if (uniform && !decodeStep) { | 821 | } else if (uniform && !decodeStep) { |
| 827 | - XTensor &mixTrans = rt.GetTensor({batch, qkvDim, seqlen}, hiddenState.dtype, DBG_LOC); | ||
| 828 | - XTensor &convOut = rt.GetTensor({batch, convDim, seqlen}, hiddenState.dtype, DBG_LOC); | ||
| 829 | XTensor mix3d; | 822 | XTensor mix3d; |
| 830 | mix3d.Init({batch, seqlen, qkvDim}, mixQkv.dtype, mixQkv.ptr); | 823 | mix3d.Init({batch, seqlen, qkvDim}, mixQkv.dtype, mixQkv.ptr); |
| 831 | - XliteOpTranspose_1_2(rt, mix3d, mixTrans); | ||
| 832 | - XliteOpConv1dAndSiLU(rt, convStateBatch, mixTrans, linearConv1d[layer], convOut, | ||
| 833 | - /*updateState=*/true); | ||
| 834 | - rt.PutTensor(mixTrans); | ||
| 835 | XTensor convSeq3d; | 824 | XTensor convSeq3d; |
| 836 | convSeq3d.Init({batch, seqlen, convDim}, convPacked.dtype, convPacked.ptr); | 825 | convSeq3d.Init({batch, seqlen, convDim}, convPacked.dtype, convPacked.ptr); |
| 837 | - XliteOpTranspose_1_2(rt, convOut, convSeq3d); | 826 | + XliteOpConv1dAndSiLU(rt, convStateBatch, mix3d, linearConv1d[layer], convSeq3d, |
| 838 | - rt.PutTensor(convOut); | 827 | + /*updateState=*/true); |
| 839 | rt.PutTensor(mixQkv); | 828 | rt.PutTensor(mixQkv); |
| 840 | } else { | 829 | } else { |
| 841 | XliteOpConv1dAndSiLU(rt, convStateBatch, mixQkv, linearConv1d[layer], convPacked, | 830 | XliteOpConv1dAndSiLU(rt, convStateBatch, mixQkv, linearConv1d[layer], convPacked, |
| @@ -1896,22 +1896,26 @@ void XliteOpConv1dAndSiLU(XRuntime &rt, XTensor &state, XTensor &input, XTensor | |||
| 1896 | } | 1896 | } |
| 1897 | } else { | 1897 | } else { |
| 1898 | if (state.shape.size() != 3 || input.shape.size() != 3 || output.shape.size() != 3) { | 1898 | if (state.shape.size() != 3 || input.shape.size() != 3 || output.shape.size() != 3) { |
| 1899 | - throw std::runtime_error("XliteOpConv1dAndSiLU: state/input/output must be 3D [B,C,*]"); | 1899 | + throw std::runtime_error("XliteOpConv1dAndSiLU: state/input/output must be 3D [B,*,C]"); |
| 1900 | } | 1900 | } |
| 1901 | - if (state.shape[0] != input.shape[0] || state.shape[1] != input.shape[1] || | 1901 | + if (state.shape[0] != input.shape[0] || output.shape[0] != input.shape[0]) { |
| 1902 | - output.shape[0] != input.shape[0] || output.shape[1] != input.shape[1] || | 1902 | + throw std::runtime_error("XliteOpConv1dAndSiLU: batch shape mismatch"); |
| 1903 | - output.shape[2] != input.shape[2]) { | 1903 | + } |
| 1904 | - throw std::runtime_error("XliteOpConv1dAndSiLU: batch/channel/seq shape mismatch"); | 1904 | + if (state.shape[1] != input.shape[2] || output.shape[2] != input.shape[2]) { |
| 1905 | + throw std::runtime_error("XliteOpConv1dAndSiLU: channel shape mismatch"); | ||
| 1906 | + } | ||
| 1907 | + if (output.shape[1] != input.shape[1]) { | ||
| 1908 | + throw std::runtime_error("XliteOpConv1dAndSiLU: seq shape mismatch"); | ||
| 1905 | } | 1909 | } |
| 1906 | if (state.shape[2] != kernelDim) { | 1910 | if (state.shape[2] != kernelDim) { |
| 1907 | throw std::runtime_error("XliteOpConv1dAndSiLU: state last dim != kernelDim"); | 1911 | throw std::runtime_error("XliteOpConv1dAndSiLU: state last dim != kernelDim"); |
| 1908 | } | 1912 | } |
| 1909 | - if (weight.shape[0] != input.shape[1]) { | 1913 | + if (weight.shape[0] != input.shape[2]) { |
| 1910 | throw std::runtime_error("XliteOpConv1dAndSiLU: weight channels mismatch"); | 1914 | throw std::runtime_error("XliteOpConv1dAndSiLU: weight channels mismatch"); |
| 1911 | } | 1915 | } |
| 1912 | batch = input.shape[0]; | 1916 | batch = input.shape[0]; |
| 1913 | - channels = input.shape[1]; | 1917 | + channels = input.shape[2]; |
| 1914 | - seqLen = input.shape[2]; | 1918 | + seqLen = input.shape[1]; |
| 1915 | if (kernelDim > 16 || seqLen > 4096) { | 1919 | if (kernelDim > 16 || seqLen > 4096) { |
| 1916 | throw std::runtime_error( | 1920 | throw std::runtime_error( |
| 1917 | "XliteOpConv1dAndSiLU: require kernelDim<=16 and seqLen<=4096 for fused kernel"); | 1921 | "XliteOpConv1dAndSiLU: require kernelDim<=16 and seqLen<=4096 for fused kernel"); |
| @@ -10,21 +10,14 @@ | |||
| 10 | import torch | 10 | import torch |
| 11 | import time | 11 | import time |
| 12 | import torch.nn.functional as F | 12 | import torch.nn.functional as F |
| 13 | -from xlite._C import Runtime, transpose_1_2, linear_att_conv_and_silu | 13 | +from xlite._C import Runtime, linear_att_conv_and_silu |
| 14 | 14 | ||
| 15 | channels = 6144 | 15 | channels = 6144 |
| 16 | kernel_dim = 4 | 16 | kernel_dim = 4 |
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | def my_impl(rt, input, weight, conv_state, output, batch, seq_len): | 19 | def my_impl(rt, input, weight, conv_state, output, batch, seq_len): |
| 20 | - mix_qkv = torch.empty(batch, channels, seq_len) | 20 | + linear_att_conv_and_silu(rt, input, conv_state, weight, output) |
| 21 | - torch.npu.synchronize() | ||
| 22 | - transpose_1_2(rt, input, mix_qkv) | ||
| 23 | - torch.npu.synchronize() | ||
| 24 | - out = torch.empty(batch, channels, seq_len) | ||
| 25 | - linear_att_conv_and_silu(rt, mix_qkv, conv_state, weight, out) | ||
| 26 | - torch.npu.synchronize() | ||
| 27 | - transpose_1_2(rt, out, output) | ||
| 28 | torch.npu.synchronize() | 21 | torch.npu.synchronize() |
| 29 | 22 | ||
| 30 | 23 | ||
| @@ -12,7 +12,7 @@ import time | |||
| 12 | import torch | 12 | import torch |
| 13 | import torch.nn.functional as F | 13 | import torch.nn.functional as F |
| 14 | from xlite._C import (Runtime, linear_att_conv_and_silu, | 14 | from xlite._C import (Runtime, linear_att_conv_and_silu, |
| 15 | - linear_att_conv_and_silu_token, transpose_1_2) | 15 | + linear_att_conv_and_silu_token) |
| 16 | 16 | ||
| 17 | kernel_dim = 4 | 17 | kernel_dim = 4 |
| 18 | 18 | ||
| @@ -69,34 +69,31 @@ def run_perf(rt, batch=1, seq_len=512, channels=10240, dtype=torch.bfloat16): | |||
| 69 | input = torch.randn(batch, seq_len, channels, dtype=dtype, device="npu:0") | 69 | input = torch.randn(batch, seq_len, channels, dtype=dtype, device="npu:0") |
| 70 | weight = torch.randn(channels, 1, kernel_dim, dtype=dtype, device="npu:0") | 70 | weight = torch.randn(channels, 1, kernel_dim, dtype=dtype, device="npu:0") |
| 71 | state_t = torch.randn(batch, channels, kernel_dim, dtype=dtype, device="npu:0") | 71 | state_t = torch.randn(batch, channels, kernel_dim, dtype=dtype, device="npu:0") |
| 72 | - state_3d = state_t.clone() | 72 | + state_chan = state_t.clone() |
| 73 | 73 | ||
| 74 | mix_qkv = input.reshape(batch * seq_len, channels).contiguous() | 74 | mix_qkv = input.reshape(batch * seq_len, channels).contiguous() |
| 75 | out_tok = torch.zeros(batch * seq_len, channels, dtype=dtype, device="npu:0") | 75 | out_tok = torch.zeros(batch * seq_len, channels, dtype=dtype, device="npu:0") |
| 76 | 76 | ||
| 77 | - # old 3D path: transpose [B,S,C]->[B,C,S], channel-parallel conv, transpose back | 77 | + # channel-parallel path: the fused conv consumes/produces [B,S,C] directly |
| 78 | - mix_trans = torch.empty(batch, channels, seq_len, dtype=dtype, device="npu:0") | 78 | + # (no Transpose passes) and updates the state in-kernel. |
| 79 | - conv_out = torch.empty(batch, channels, seq_len, dtype=dtype, device="npu:0") | 79 | + out_chan = torch.empty(batch, seq_len, channels, dtype=dtype, device="npu:0") |
| 80 | - out_3d = torch.empty(batch, seq_len, channels, dtype=dtype, device="npu:0") | ||
| 81 | torch.npu.synchronize() # torch stream -> rt stream barrier | 80 | torch.npu.synchronize() # torch stream -> rt stream barrier |
| 82 | 81 | ||
| 83 | def token_path(): | 82 | def token_path(): |
| 84 | linear_att_conv_and_silu_token(rt, mix_qkv, state_t, weight, out_tok, seq_len) | 83 | linear_att_conv_and_silu_token(rt, mix_qkv, state_t, weight, out_tok, seq_len) |
| 85 | 84 | ||
| 86 | - def old_3d_path(): | 85 | + def chan_path(): |
| 87 | - transpose_1_2(rt, input, mix_trans) | 86 | + linear_att_conv_and_silu(rt, input, state_chan, weight, out_chan) |
| 88 | - linear_att_conv_and_silu(rt, mix_trans, state_3d, weight, conv_out) | ||
| 89 | - transpose_1_2(rt, conv_out, out_3d) | ||
| 90 | 87 | ||
| 91 | t_tok = bench(token_path) | 88 | t_tok = bench(token_path) |
| 92 | - t_old = bench(old_3d_path) | 89 | + t_chan = bench(chan_path) |
| 93 | # numerical cross-check while we are here | 90 | # numerical cross-check while we are here |
| 94 | - torch.testing.assert_close(out_tok.reshape(batch, seq_len, channels), out_3d, | 91 | + torch.testing.assert_close(out_tok.reshape(batch, seq_len, channels), out_chan, |
| 95 | rtol=1e-2, atol=1e-3) | 92 | rtol=1e-2, atol=1e-3) |
| 96 | - torch.testing.assert_close(state_t, state_3d, rtol=1e-2, atol=1e-3) | 93 | + torch.testing.assert_close(state_t, state_chan, rtol=1e-2, atol=1e-3) |
| 97 | print(f"[perf B={batch} S={seq_len} C={channels} {dtype}] " | 94 | print(f"[perf B={batch} S={seq_len} C={channels} {dtype}] " |
| 98 | - f"token={t_tok:.3f} ms/call, old3d={t_old:.3f} ms/call, " | 95 | + f"token={t_tok:.3f} ms/call, chan={t_chan:.3f} ms/call, " |
| 99 | - f"speedup={t_old / t_tok:.2f}x") | 96 | + f"speedup={t_chan / t_tok:.2f}x") |
| 100 | 97 | ||
| 101 | 98 | ||
| 102 | if __name__ == "__main__": | 99 | if __name__ == "__main__": |
| @@ -2514,10 +2514,10 @@ def linear_att_conv_and_silu( | |||
| 2514 | 2514 | ||
| 2515 | Args: | 2515 | Args: |
| 2516 | rt (Runtime): Native runtime handle. | 2516 | rt (Runtime): Native runtime handle. |
| 2517 | - mix_qkv (torch.Tensor): Input mixed QKV tensor, shape [B, C, S]. | 2517 | + mix_qkv (torch.Tensor): Input mixed QKV tensor, shape [B, S, C]. |
| 2518 | conv_state (torch.Tensor): Convolution state tensor, shape [B, C, K]. | 2518 | conv_state (torch.Tensor): Convolution state tensor, shape [B, C, K]. |
| 2519 | weight (torch.Tensor): Kernel weight tensor, shape [C, 1, K] or [C, K]. | 2519 | weight (torch.Tensor): Kernel weight tensor, shape [C, 1, K] or [C, K]. |
| 2520 | - output (torch.Tensor): Output tensor, shape [B, C, S]. | 2520 | + output (torch.Tensor): Output tensor, shape [B, S, C]. |
| 2521 | 2521 | ||
| 2522 | Returns: | 2522 | Returns: |
| 2523 | None: `output` is written in place. State is always updated. | 2523 | None: `output` is written in place. State is always updated. |