11# Copyright (c) 2026 BAAI. All rights reserved.
22
3- """GCU fix for _per_token_group_quant_fp8 / _per_token_group_quant_fp8_colmajor .
3+ """GCU fix for per_token_group_quant_fp8 / per_token_group_quant_fp8_colmajor .
44
5- GCU hardware limits grid.x to 65535 and grid.y/grid.z to 255. The upstream
6- launcher uses ``grid = (M,)`` where ``M = numel // group_size``, which exceeds
7- the limit for large tensors (e.g. 8192 × 4096 ÷ 128 = 262144).
5+ GCU (L600/Libra) hardware limits:
6+ - Grid (SP 数量): <= 48 (1-D only, no 2-D grid)
7+ - DSM (片上共享存储): <= 448 KB
8+ - num_warps: <= 4
9+ - 禁止 int64 索引,统一使用 int32
810
9- This patch converts the 1-D grid into a 2-D grid that respects GCU limits,
10- following the same pattern as ``fused_recurrent_packed_decode.py``.
11+ The upstream launcher uses ``grid = (M,)`` where ``M = numel // group_size``,
12+ which exceeds the limit for large tensors (e.g. 8192 × 4096 ÷ 128 = 262144).
13+
14+ This patch converts the launch to GCU Fixed-Grid + strided-loop pattern:
15+ - grid = (min(M, GCU_NUM_GRID),) — 1-D only
16+ - kernel 内跨步循环 for g_id in range(pid, M, GCU_NUM_GRID)
1117"""
1218
1319from __future__ import annotations
2026
2127logger = logging .getLogger (__name__ )
2228
23- GCU_MAX_GRID_X = 65535
24- GCU_MAX_GRID_YZ = 255
29+ # ---------------------------------------------------------------------------
30+ # GCU (L600 / Libra) hardware constants
31+ # ---------------------------------------------------------------------------
32+ # S60 板卡: GCU_NUM_GRID = 24, GCU_MAX_DSM_MEMORY = int(1.5 * 1024 * 1024)
33+ # Libra 板卡: GCU_NUM_GRID = 48, GCU_MAX_DSM_MEMORY = 917504 // 2
34+ GCU_NUM_GRID = 48
35+ GCU_MAX_DSM_MEMORY = 917504 // 2 # 448 KB
36+
2537_patched = False
2638
2739
2840# ---------------------------------------------------------------------------
2941# Grid helpers
3042# ---------------------------------------------------------------------------
3143
32- def _gcu_grid (total : int ) -> tuple [int , int ]:
33- """Split *total* work-items into a 2-D grid respecting GCU hardware limits."""
34- grid_x = min (total , GCU_MAX_GRID_X )
35- grid_y = triton .cdiv (total , grid_x )
36- if grid_y > GCU_MAX_GRID_YZ :
37- grid_y = GCU_MAX_GRID_YZ
38- grid_x = triton .cdiv (total , grid_y )
39- grid_x = min (grid_x , GCU_MAX_GRID_X )
40- return grid_x , grid_y
44+ def _gcu_grid (total : int ) -> tuple [int , ...]:
45+ """Return a 1-D grid tuple respecting GCU hardware limits (grid <= GCU_NUM_GRID).
46+
47+ GCU only supports 1-D grid; the kernel uses a strided loop internally
48+ to cover all *total* work-items.
49+ """
50+ return (min (total , GCU_NUM_GRID ),)
4151
4252
4353# ---------------------------------------------------------------------------
44- # GCU-compatible Triton kernels
54+ # GCU-compatible Triton kernels (Fixed-Grid + strided loop)
4555# ---------------------------------------------------------------------------
4656
57+
4758@triton .jit
4859def _per_token_group_quant_fp8_gcu (
4960 # Pointers to inputs and output
5061 y_ptr ,
5162 y_q_ptr ,
5263 y_s_ptr ,
64+ # Total number of groups (M = numel // group_size)
65+ total_groups ,
5366 group_size ,
5467 # Num columns of y
5568 y_num_columns ,
@@ -62,46 +75,49 @@ def _per_token_group_quant_fp8_gcu(
6275 use_ue8m0 : tl .constexpr ,
6376 # Meta-parameters
6477 BLOCK : tl .constexpr ,
78+ NUM_SPC : tl .constexpr ,
6579):
66- """A Triton-accelerated function to perform per-token-group
67- quantization on a tensor.
80+ """GCU Fixed-Grid per-token-group FP8 quantization (row-major scales).
6881
69- GCU variant: uses 2-D grid (program_id 0 + 1) so that grid.x never
70- exceeds the hardware limit of 65535.
82+ Uses 1-D Fixed Grid + strided loop so that grid never exceeds
83+ GCU_NUM_GRID (48 for L600). Each program loops over multiple groups
84+ via ``for g_id in range(pid, total_groups, NUM_SPC)``.
7185 """
7286 groups_per_row = y_num_columns // group_size
87+ pid = tl .program_id (0 )
7388
74- # Map the 2-D program id to a flat group id .
75- g_id = tl . program_id ( 0 ) + tl . program_id ( 1 ) * tl . num_programs ( 0 )
76- row = g_id // groups_per_row
77- row_g_id = g_id % groups_per_row
89+ # Strided loop: each SP handles work items pid, pid+NUM_SPC, pid+2*NUM_SPC, .. .
90+ for g_id in range ( pid , total_groups , NUM_SPC ):
91+ row = g_id // groups_per_row
92+ row_g_id = g_id % groups_per_row
7893
79- # Ensure offset calculations use int64 to prevent overflow
80- y_ptr_offset = (row .to (tl .int64 ) * y_row_stride ) + (
81- row_g_id .to (tl .int64 ) * group_size
82- )
83- y_ptr += y_ptr_offset
94+ # Offset calculations use int32 (GCU constraint: no int64 indexing)
95+ y_ptr_offset = (
96+ row .to (tl .int32 ) * y_row_stride .to (tl .int32 )
97+ + row_g_id .to (tl .int32 ) * group_size .to (tl .int32 )
98+ )
99+ y_cur = y_ptr + y_ptr_offset
100+
101+ y_q_cur = y_q_ptr + g_id .to (tl .int32 ) * group_size .to (tl .int32 )
102+ y_s_cur = y_s_ptr + g_id
84103
85- y_q_ptr_offset = g_id .to (tl .int64 ) * group_size
86- y_q_ptr += y_q_ptr_offset
87- y_s_ptr += g_id
104+ cols = tl .arange (0 , BLOCK ) # group_size <= BLOCK
105+ mask = cols < group_size
88106
89- cols = tl .arange (0 , BLOCK ) # N <= BLOCK
90- mask = cols < group_size
107+ y = tl .load (y_cur + cols , mask = mask , other = 0.0 ).to (tl .float32 )
91108
92- y = tl .load (y_ptr + cols , mask = mask , other = 0.0 ).to (tl .float32 )
93- # Quant
94- # Use multiply-by-reciprocal instead of division to match PyTorch's
95- # tensor/scalar division precision (GPU fast-division for constexpr
96- # divisors can introduce 1-ULP error that flips FP8 quantization at
97- # representable-value boundaries).
98- _absmax = tl .maximum (tl .max (tl .abs (y )), eps )
99- scale_raw = _absmax * (1.0 / fp8_max )
100- y_s = tl .math .exp2 (tl .ceil (tl .log2 (scale_raw ))) if use_ue8m0 else scale_raw
101- y_q = tl .clamp (y / y_s , fp8_min , fp8_max ).to (y_q_ptr .dtype .element_ty )
109+ # Quant — multiply-by-reciprocal avoids GPU fast-division 1-ULP error
110+ _absmax = tl .maximum (tl .max (tl .abs (y )), eps )
111+ scale_raw = _absmax * (1.0 / fp8_max )
112+ y_s = (
113+ tl .math .exp2 (tl .ceil (tl .log2 (scale_raw )))
114+ if use_ue8m0
115+ else scale_raw
116+ )
117+ y_q = tl .clamp (y / y_s , fp8_min , fp8_max ).to (y_q_ptr .dtype .element_ty )
102118
103- tl .store (y_q_ptr + cols , y_q , mask = mask )
104- tl .store (y_s_ptr , y_s )
119+ tl .store (y_q_cur + cols , y_q , mask = mask )
120+ tl .store (y_s_cur , y_s )
105121
106122
107123@triton .jit
@@ -110,6 +126,8 @@ def _per_token_group_quant_fp8_colmajor_gcu(
110126 y_ptr ,
111127 y_q_ptr ,
112128 y_s_ptr ,
129+ # Total number of groups (M = numel // group_size)
130+ total_groups ,
113131 group_size ,
114132 # Num columns of y
115133 y_num_columns ,
@@ -124,50 +142,55 @@ def _per_token_group_quant_fp8_colmajor_gcu(
124142 use_ue8m0 : tl .constexpr ,
125143 # Meta-parameters
126144 BLOCK : tl .constexpr ,
145+ NUM_SPC : tl .constexpr ,
127146):
128- """A Triton-accelerated function to perform per-token-group
129- quantization on a tensor (column-major scales).
147+ """GCU Fixed-Grid per-token-group FP8 quantization (column-major scales).
130148
131- GCU variant: uses 2 -D grid (program_id 0 + 1) so that grid.x never
132- exceeds the hardware limit of 65535 .
149+ Uses 1 -D Fixed Grid + strided loop. Scale tensor is written in
150+ column-major order (shape [M, sf_k], stride [1, tma_aligned_m]) .
133151 """
134152 groups_per_row = y_num_columns // group_size
153+ pid = tl .program_id (0 )
135154
136- # Map the 2-D program id to a flat group id.
137- g_id = tl .program_id (0 ) + tl .program_id (1 ) * tl .num_programs (0 )
138- row = g_id // groups_per_row
139- row_g_id = g_id % groups_per_row
155+ for g_id in range (pid , total_groups , NUM_SPC ):
156+ row = g_id // groups_per_row
157+ row_g_id = g_id % groups_per_row
140158
141- # Ensure offset calculations use int64 to prevent overflow
142- y_ptr_offset = (row .to (tl .int64 ) * y_row_stride ) + (
143- row_g_id .to (tl .int64 ) * group_size
144- )
145- y_ptr += y_ptr_offset
159+ y_ptr_offset = (
160+ row .to (tl .int32 ) * y_row_stride .to (tl .int32 )
161+ + row_g_id .to (tl .int32 ) * group_size .to (tl .int32 )
162+ )
163+ y_cur = y_ptr + y_ptr_offset
164+
165+ y_q_cur = y_q_ptr + g_id .to (tl .int32 ) * group_size .to (tl .int32 )
146166
147- y_q_ptr_offset = g_id .to (tl .int64 ) * group_size
148- y_q_ptr += y_q_ptr_offset
167+ # Column-major scale indexing
168+ blocks_per_row = groups_per_row
169+ scale_col = g_id % blocks_per_row
170+ scale_row = g_id // blocks_per_row
171+ y_s_offset = (
172+ scale_col .to (tl .int32 ) * y_s_col_stride .to (tl .int32 )
173+ + scale_row .to (tl .int32 )
174+ )
175+ y_s_cur = y_s_ptr + y_s_offset
149176
150- # Convert g_id the flattened block coordinate to 2D so we can index
151- # into the output y_scales matrix
152- blocks_per_row = y_num_columns // group_size
153- scale_col = g_id % blocks_per_row
154- scale_row = g_id // blocks_per_row
155- # Ensure offset calculation uses int64 for y_s_ptr
156- y_s_ptr_offset = (scale_col .to (tl .int64 ) * y_s_col_stride ) + scale_row .to (tl .int64 )
157- y_s_ptr += y_s_ptr_offset
177+ cols = tl .arange (0 , BLOCK )
178+ mask = cols < group_size
158179
159- cols = tl .arange (0 , BLOCK ) # group_size <= BLOCK
160- mask = cols < group_size
180+ y = tl .load (y_cur + cols , mask = mask , other = 0.0 ).to (tl .float32 )
161181
162- y = tl .load (y_ptr + cols , mask = mask , other = 0.0 ).to (tl .float32 )
163- # Quant
164- _absmax = tl .maximum (tl .max (tl .abs (y )), eps )
165- scale_raw = _absmax * (1.0 / fp8_max )
166- y_s = tl .math .exp2 (tl .ceil (tl .log2 (scale_raw ))) if use_ue8m0 else scale_raw
167- y_q = tl .clamp (y / y_s , fp8_min , fp8_max ).to (y_q_ptr .dtype .element_ty )
182+ # Quant
183+ _absmax = tl .maximum (tl .max (tl .abs (y )), eps )
184+ scale_raw = _absmax * (1.0 / fp8_max )
185+ y_s = (
186+ tl .math .exp2 (tl .ceil (tl .log2 (scale_raw )))
187+ if use_ue8m0
188+ else scale_raw
189+ )
190+ y_q = tl .clamp (y / y_s , fp8_min , fp8_max ).to (y_q_ptr .dtype .element_ty )
168191
169- tl .store (y_q_ptr + cols , y_q , mask = mask )
170- tl .store (y_s_ptr , y_s )
192+ tl .store (y_q_cur + cols , y_q , mask = mask )
193+ tl .store (y_s_cur , y_s )
171194
172195
173196# ---------------------------------------------------------------------------
@@ -187,8 +210,8 @@ def per_token_group_quant_fp8_gcu(
187210 """GCU-compatible version of ``per_token_group_quant_fp8``.
188211
189212 Identical semantics to the upstream function, but launches Triton kernels
190- with a 2-D grid that respects GCU hardware limits
191- (grid.x ≤ 65535, grid.y ≤ 255) .
213+ with GCU Fixed-Grid (1-D, ≤48) + strided-loop pattern instead of a
214+ potentially oversized 1-D grid.
192215 """
193216 from vllm .model_executor .layers .quantization .utils .quant_utils import (
194217 get_fp8_min_max ,
@@ -255,46 +278,60 @@ def per_token_group_quant_fp8_gcu(
255278 )
256279 return x_q , x_s
257280
258- # TRITON FALLBACK – use 2-D grid for GCU
259- M = x .numel () // group_size
281+ # --- GCU Triton fallback: Fixed-Grid + strided loop ---
282+ M = x .numel () // group_size # total number of groups
260283 N = group_size
261284 BLOCK = triton .next_power_of_2 (N )
262- # heuristics for number of warps
263- num_warps = min (max (BLOCK // 256 , 1 ), 8 )
285+
286+ # Heuristics for number of warps (GCU: must be <= 4)
287+ num_warps = min (max (BLOCK // 256 , 1 ), 4 )
264288 num_stages = 1
265289
290+ # DSM sanity check (GCU L600: ≤ 448 KB)
291+ # Peak vectors per iteration: y (input) + y_q (output) ≈ 2
292+ # For BLOCK=128, fp16: 2 * 128 * 2 = 512 bytes — well within 448 KB
293+ _dsm_bytes = 2 * BLOCK * x .element_size ()
294+ assert _dsm_bytes <= GCU_MAX_DSM_MEMORY , (
295+ f"DSM estimate { _dsm_bytes } bytes exceeds GCU limit "
296+ f"{ GCU_MAX_DSM_MEMORY } bytes. Reduce group_size or BLOCK."
297+ )
298+
299+ # 1-D Fixed Grid (GCU only supports 1-D grid)
266300 grid = _gcu_grid (M )
301+ assert grid [0 ] <= GCU_NUM_GRID , (
302+ f"Grid { grid [0 ]} exceeds GCU_NUM_GRID { GCU_NUM_GRID } "
303+ )
267304
268305 if column_major_scales :
269306 _per_token_group_quant_fp8_colmajor_gcu [grid ](
270- x ,
271- x_q ,
272- x_s ,
273- group_size ,
274- x .shape [1 ],
275- x .stride (0 ),
276- x_s .stride (1 ),
277- eps ,
307+ x , x_q , x_s ,
308+ total_groups = M ,
309+ group_size = group_size ,
310+ y_num_columns = x .shape [1 ],
311+ y_row_stride = x .stride (0 ),
312+ y_s_col_stride = x_s .stride (1 ),
313+ eps = eps ,
278314 fp8_min = fp8_min ,
279315 fp8_max = fp8_max ,
280316 use_ue8m0 = use_ue8m0 ,
281317 BLOCK = BLOCK ,
318+ NUM_SPC = GCU_NUM_GRID ,
282319 num_warps = num_warps ,
283320 num_stages = num_stages ,
284321 )
285322 else :
286323 _per_token_group_quant_fp8_gcu [grid ](
287- x ,
288- x_q ,
289- x_s ,
290- group_size ,
291- x .shape [1 ],
292- x .stride (0 ),
293- eps ,
324+ x , x_q , x_s ,
325+ total_groups = M ,
326+ group_size = group_size ,
327+ y_num_columns = x .shape [1 ],
328+ y_row_stride = x .stride (0 ),
329+ eps = eps ,
294330 fp8_min = fp8_min ,
295331 fp8_max = fp8_max ,
296332 use_ue8m0 = use_ue8m0 ,
297333 BLOCK = BLOCK ,
334+ NUM_SPC = GCU_NUM_GRID ,
298335 num_warps = num_warps ,
299336 num_stages = num_stages ,
300337 )
@@ -493,15 +530,15 @@ def apply_per_token_group_quant_fp8_gcu_patch() -> None:
493530 except ImportError :
494531 continue
495532 if hasattr (mod , "per_token_group_quant_fp8" ):
496- # mod.per_token_group_quant_fp8 = per_token_group_quant_fp8_gcu
497- mod .per_token_group_quant_fp8 = per_token_group_quant_fp8_torch
533+ mod .per_token_group_quant_fp8 = per_token_group_quant_fp8_gcu
498534
499535 _patched = True
500536 logger .info (
501537 "Patched per_token_group_quant_fp8 for GCU "
502- "(grid.x <= %d, grid.y <= %d) in %d modules" ,
503- GCU_MAX_GRID_X ,
504- GCU_MAX_GRID_YZ ,
538+ "(grid <= %d, 1-D only, NUM_SPC=%d, DSM <= %d KB) in %d modules" ,
539+ GCU_NUM_GRID ,
540+ GCU_NUM_GRID ,
541+ GCU_MAX_DSM_MEMORY // 1024 ,
505542 len (_IMPORTERS ),
506543 )
507544 except Exception as exc :
0 commit comments