Diff
checker
텍스트
텍스트
이미지
문서
Excel
폴더
Legal
Enterprise
데스크톱
요금제
로그인
데스크톱 앱 다운로드
텍스트 비교
두 텍스트 파일의 차이점을 찾아보세요
도구
기록
실시간 편집
공백 변경 숨기기
변경 없는 행 숨기기
줄바꿈 비활성화
레이아웃
나란히 보기
합쳐 보기
비교 단위
스마트
단어
글자
텍스트 스타일
모양 변경
구문 강조
언어 선택
제외
텍스트 변환
첫 변경으로
수정
Diffchecker Desktop
가장 안전하게 Diffchecker를 사용하는 방법. 데스크톱 앱을 사용하면 비교 데이터가 외부로 전송되지 않습니다!
데스크톱 앱 받기
Comparing TLX between persistent and CLC
생성일
8개월 전
비교 결과 만료 없음
초기화
내보내기
공유
설명
35 삭제
행
총
삭제
글자
총
삭제
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
158 행
복사
54 추가
행
총
추가
글자
총
추가
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
191 행
복사
복사
복사됨
복사
복사됨
@triton.jit
def matmul_kernel_tma_ws_blackwell_
clc
(a_desc, b_desc, c_desc, M, N, K, BLOCK_SIZE_M: tl.constexpr,
def matmul_kernel_tma_ws_blackwell_
persistent
(a_desc, b_desc, c_desc, M, N, K, BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr, #
BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr, #
GROUP_SIZE_M: tl.constexpr, #
GROUP_SIZE_M: tl.constexpr, #
NUM_SMEM_BUFFERS: tl.constexpr, #
NUM_SMEM_BUFFERS: tl.constexpr, #
NUM_TMEM_BUFFERS: tl.constexpr, #
NUM_TMEM_BUFFERS: tl.constexpr, #
NUM_SMS: tl.constexpr, #
NUM_SMS: tl.constexpr, #
NUM_CLC_STAGES: tl.constexpr, #
EPILOGUE_SUBTILE: tl.constexpr, #
EPILOGUE_SUBTILE: tl.constexpr, #
):
):
# allocate NUM_SMEM_BUFFERS buffers
# allocate NUM_SMEM_BUFFERS buffers
buffers_A = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_K), tl.float16, NUM_SMEM_BUFFERS)
buffers_A = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_K), tl.float16, NUM_SMEM_BUFFERS)
buffers_B = tlx.local_alloc((BLOCK_SIZE_K, BLOCK_SIZE_N), tl.float16, NUM_SMEM_BUFFERS)
buffers_B = tlx.local_alloc((BLOCK_SIZE_K, BLOCK_SIZE_N), tl.float16, NUM_SMEM_BUFFERS)
# use multiple TMEM buffers to overlap MMA and epilogue
# use multiple TMEM buffers to overlap MMA and epilogue
tmem_buffers = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_N), tl.float32, NUM_TMEM_BUFFERS, tlx.storage_kind.tmem)
tmem_buffers = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_N), tl.float32, NUM_TMEM_BUFFERS, tlx.storage_kind.tmem)
# allocate barriers
# allocate barriers
smem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1)
smem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1)
smem_full_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1)
smem_full_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1)
tmem_full_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1)
tmem_full_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1)
tmem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1)
tmem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1)
복사
복사됨
복사
복사됨
clc_context = tlx.clc_create_context(NUM_CLC_STAGES, 3)
with tlx.async_tasks():
with tlx.async_tasks():
with tlx.async_task("default"): # epilogue consumer
with tlx.async_task("default"): # epilogue consumer
# common code duplicated for each region to avoid SMEM overhead
# common code duplicated for each region to avoid SMEM overhead
start_pid = tl.program_id(axis=0)
start_pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
num_pid_in_group = GROUP_SIZE_M * num_pid_n
복사
복사됨
복사
복사됨
num_tiles = num_pid_m * num_pid_n
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
# end of common code
# end of common code
tmem_read_phase = 0
tmem_read_phase = 0
cur_tmem_buf = 0
cur_tmem_buf = 0
복사
복사됨
복사
복사됨
for
tile_id
in range(
start_pid
, num_tiles, NUM_SMS):
tile_id
=
start_pid
clc_phase_producer = 1
clc_phase_consumer = 0
clc_buf = 0
while tile_id != -1:
clc_buf = clc_buf % NUM_CLC_STAGES
# Debug prints
# if tlx.thread_id(axis=0) == 0:
# tl.device_print("Default WG Processing CtaID", tile_id)
# producer
tlx.clc_producer(clc_context, clc_buf, clc_phase_producer)
# clc_phase_producer ^= 1
clc_phase_producer = clc_phase_producer ^ (clc_buf == (NUM_CLC_STAGES - 1))
pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M)
pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M)
offs_am = pid_m * BLOCK_SIZE_M
offs_am = pid_m * BLOCK_SIZE_M
offs_bn = pid_n * BLOCK_SIZE_N
offs_bn = pid_n * BLOCK_SIZE_N
tlx.barrier_wait(tmem_full_bars[cur_tmem_buf], tmem_read_phase)
tlx.barrier_wait(tmem_full_bars[cur_tmem_buf], tmem_read_phase)
# flip phase at the end of a round of using TMEM barriers
# flip phase at the end of a round of using TMEM barriers
tmem_read_phase = tmem_read_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1)
tmem_read_phase = tmem_read_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1)
# load the result from TMEM to registers
# load the result from TMEM to registers
acc_tmem = tmem_buffers[cur_tmem_buf]
acc_tmem = tmem_buffers[cur_tmem_buf]
if EPILOGUE_SUBTILE:
if EPILOGUE_SUBTILE:
# We load/store the result half by half to reduce SMEM pressure
# We load/store the result half by half to reduce SMEM pressure
acc_tmem_subslice1 = tlx.subslice(acc_tmem, 0, BLOCK_SIZE_N // 2)
acc_tmem_subslice1 = tlx.subslice(acc_tmem, 0, BLOCK_SIZE_N // 2)
result = tlx.local_load(acc_tmem_subslice1)
result = tlx.local_load(acc_tmem_subslice1)
c = result.to(tl.float16)
c = result.to(tl.float16)
c_desc.store([offs_am, offs_bn], c)
c_desc.store([offs_am, offs_bn], c)
acc_tmem_subslice2 = tlx.subslice(acc_tmem, BLOCK_SIZE_N // 2, BLOCK_SIZE_N // 2)
acc_tmem_subslice2 = tlx.subslice(acc_tmem, BLOCK_SIZE_N // 2, BLOCK_SIZE_N // 2)
result = tlx.local_load(acc_tmem_subslice2)
result = tlx.local_load(acc_tmem_subslice2)
c = result.to(tl.float16)
c = result.to(tl.float16)
c_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], c)
c_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], c)
else:
else:
result = tlx.local_load(acc_tmem)
result = tlx.local_load(acc_tmem)
c = result.to(tl.float16)
c = result.to(tl.float16)
c_desc.store([offs_am, offs_bn], c)
c_desc.store([offs_am, offs_bn], c)
# done storing this buffer, signal MMA consumer to resume writing to it
# done storing this buffer, signal MMA consumer to resume writing to it
tlx.barrier_arrive(tmem_empty_bars[cur_tmem_buf], 1)
tlx.barrier_arrive(tmem_empty_bars[cur_tmem_buf], 1)
cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS
cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS
복사
복사됨
복사
복사됨
tile_id = tlx.clc_consumer(clc_context, clc_buf, clc_phase_consumer)
# clc_phase_consumer ^= 1
clc_phase_consumer = clc_phase_consumer ^ (clc_buf == (NUM_CLC_STAGES - 1))
clc_buf += 1
# Debug-only: verifying that CLC steals workloads successfully
# if tlx.thread_id(axis=0) == 0:
# tl.device_print("Extracted CtaID", tile_id)
with tlx.async_task(num_warps=1, num_regs=232): # MMA consumer
with tlx.async_task(num_warps=1, num_regs=232): # MMA consumer
# common code duplicated for each region to avoid SMEM overhead
# common code duplicated for each region to avoid SMEM overhead
start_pid = tl.program_id(axis=0)
start_pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
num_pid_in_group = GROUP_SIZE_M * num_pid_n
복사
복사됨
복사
복사됨
num_tiles = num_pid_m * num_pid_n
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
# end of common code
# end of common code
dot_phase = 0 # the current phase of dot op
dot_phase = 0 # the current phase of dot op
tmem_write_phase = 1 # sync between epilogue consumer and MMA consumer
tmem_write_phase = 1 # sync between epilogue consumer and MMA consumer
cur_tmem_buf = 0
cur_tmem_buf = 0
processed_k_iters = 0
processed_k_iters = 0
복사
복사됨
복사
복사됨
for
tile_id
in range(
start_pid
, num_tiles,
NUM_
SMS):
tile_id
=
start_pid
clc_phase = 0
clc_buf = 0
while tile_id != -1:
clc_buf = clc_buf %
NUM_
CLC_STAGES
pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M)
pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M)
offs_am = pid_m * BLOCK_SIZE_M
offs_am = pid_m * BLOCK_SIZE_M
offs_bn = pid_n * BLOCK_SIZE_N
offs_bn = pid_n * BLOCK_SIZE_N
# wait epilogue consumer to be done with the buffer before reusing it
# wait epilogue consumer to be done with the buffer before reusing it
tlx.barrier_wait(tmem_empty_bars[cur_tmem_buf], tmem_write_phase)
tlx.barrier_wait(tmem_empty_bars[cur_tmem_buf], tmem_write_phase)
# flip phase at the end of a round of using TMEM barriers
# flip phase at the end of a round of using TMEM barriers
tmem_write_phase = tmem_write_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1)
tmem_write_phase = tmem_write_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1)
# now iterate along K to compute result for the block
# now iterate along K to compute result for the block
for k in range(0, k_tiles):
for k in range(0, k_tiles):
# processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1
# processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1
buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS
buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS
# wait for current phase(round) of load for this buf
# wait for current phase(round) of load for this buf
tlx.barrier_wait(smem_full_bars[buf], dot_phase)
tlx.barrier_wait(smem_full_bars[buf], dot_phase)
# buffer is now ready with loaded data, tlx.async_dot will signal `mBarrier` when done
# buffer is now ready with loaded data, tlx.async_dot will signal `mBarrier` when done
복사
복사됨
복사
복사됨
tlx.async_dot(
tlx.async_dot(
buffers_A[buf],
buffers_B[buf],
tmem_buffers[cur_tmem_buf],
use_acc=k > 0,
buffers_A[buf],
mBarriers=[smem_empty_bars[buf]],
out_dtype=tl.float32
)
buffers_B[buf],
tmem_buffers[cur_tmem_buf],
use_acc=k > 0,
mBarriers=[smem_empty_bars[buf]],
out_dtype=tl.float32
,
)
# flip phase at the end of a round
# flip phase at the end of a round
dot_phase = dot_phase ^ (buf == NUM_SMEM_BUFFERS - 1)
dot_phase = dot_phase ^ (buf == NUM_SMEM_BUFFERS - 1)
# wait for last mma to complete
# wait for last mma to complete
last_buf = (processed_k_iters + k_tiles - 1) % NUM_SMEM_BUFFERS
last_buf = (processed_k_iters + k_tiles - 1) % NUM_SMEM_BUFFERS
# in case phase was flipped, we should use the phase value when dot op was issued
# in case phase was flipped, we should use the phase value when dot op was issued
last_dot_phase = dot_phase ^ (last_buf == NUM_SMEM_BUFFERS - 1)
last_dot_phase = dot_phase ^ (last_buf == NUM_SMEM_BUFFERS - 1)
tlx.barrier_wait(smem_empty_bars[last_buf], last_dot_phase)
tlx.barrier_wait(smem_empty_bars[last_buf], last_dot_phase)
# done filling this buffer, signal epilogue consumer
# done filling this buffer, signal epilogue consumer
tlx.barrier_arrive(tmem_full_bars[cur_tmem_buf], 1)
tlx.barrier_arrive(tmem_full_bars[cur_tmem_buf], 1)
# possibly enter next iteration (next tile) without waiting for epilogue
# possibly enter next iteration (next tile) without waiting for epilogue
cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS
cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS
processed_k_iters += k_tiles
processed_k_iters += k_tiles
복사
복사됨
복사
복사됨
tile_id = tlx.clc_consumer(clc_context, clc_buf, clc_phase)
# clc_phase ^= 1
clc_phase = clc_phase ^ (clc_buf == (NUM_CLC_STAGES - 1))
clc_buf += 1
with tlx.async_task(num_warps=1, num_regs=232): # producer, TMA load
with tlx.async_task(num_warps=1, num_regs=232): # producer, TMA load
# common code duplicated for each region to avoid SMEM overhead
# common code duplicated for each region to avoid SMEM overhead
start_pid = tl.program_id(axis=0)
start_pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
num_pid_in_group = GROUP_SIZE_M * num_pid_n
복사
복사됨
복사
복사됨
num_tiles = num_pid_m * num_pid_n
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
# end of common code
# end of common code
load_phase = 0 # the current phase of TMA load
load_phase = 0 # the current phase of TMA load
# we virtually "flatten" the two layer loop as if we're performing tma loads on
# we virtually "flatten" the two layer loop as if we're performing tma loads on
# one big list of data
# one big list of data
processed_k_iters = 0
processed_k_iters = 0
복사
복사됨
복사
복사됨
for
tile_id
in range(
start_pid
, num_tiles,
NUM_
SMS):
tile_id
=
start_pid
clc_phase = 0
clc_buf = 0
while tile_id != -1:
clc_buf = clc_buf %
NUM_
CLC_STAGES
pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M)
pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M)
offs_am = pid_m * BLOCK_SIZE_M
offs_am = pid_m * BLOCK_SIZE_M
offs_bn = pid_n * BLOCK_SIZE_N
offs_bn = pid_n * BLOCK_SIZE_N
for k in range(0, k_tiles):
for k in range(0, k_tiles):
# processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1
# processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1
buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS
buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS
# wait for previous phase(round) of dot for this buf
# wait for previous phase(round) of dot for this buf
tlx.barrier_wait(smem_empty_bars[buf], load_phase ^ 1)
tlx.barrier_wait(smem_empty_bars[buf], load_phase ^ 1)
# buffer is now ready to be used again
# buffer is now ready to be used again
offs_k = k * BLOCK_SIZE_K
offs_k = k * BLOCK_SIZE_K
tlx.barrier_expect_bytes(smem_full_bars[buf],
tlx.barrier_expect_bytes(smem_full_bars[buf],
2 * (BLOCK_SIZE_M + BLOCK_SIZE_N) * BLOCK_SIZE_K) # float16
2 * (BLOCK_SIZE_M + BLOCK_SIZE_N) * BLOCK_SIZE_K) # float16
tlx.async_descriptor_load(a_desc, buffers_A[buf], [offs_am, offs_k], smem_full_bars[buf])
tlx.async_descriptor_load(a_desc, buffers_A[buf], [offs_am, offs_k], smem_full_bars[buf])
tlx.async_descriptor_load(b_desc, buffers_B[buf], [offs_k, offs_bn], smem_full_bars[buf])
tlx.async_descriptor_load(b_desc, buffers_B[buf], [offs_k, offs_bn], smem_full_bars[buf])
# flip phase at the end of a round
# flip phase at the end of a round
load_phase = load_phase ^ (buf == NUM_SMEM_BUFFERS - 1)
load_phase = load_phase ^ (buf == NUM_SMEM_BUFFERS - 1)
processed_k_iters += k_tiles
processed_k_iters += k_tiles
복사
복사됨
복사
복사됨
tile_id = tlx.clc_consumer(clc_context, clc_buf, clc_phase)
# clc_phase ^= 1
clc_phase = clc_phase ^ (clc_buf == (NUM_CLC_STAGES - 1))
clc_buf += 1
저장된 비교 결과
원본
파일 열기
@triton.jit def matmul_kernel_tma_ws_blackwell_persistent(a_desc, b_desc, c_desc, M, N, K, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr, # GROUP_SIZE_M: tl.constexpr, # NUM_SMEM_BUFFERS: tl.constexpr, # NUM_TMEM_BUFFERS: tl.constexpr, # NUM_SMS: tl.constexpr, # EPILOGUE_SUBTILE: tl.constexpr, # ): # allocate NUM_SMEM_BUFFERS buffers buffers_A = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_K), tl.float16, NUM_SMEM_BUFFERS) buffers_B = tlx.local_alloc((BLOCK_SIZE_K, BLOCK_SIZE_N), tl.float16, NUM_SMEM_BUFFERS) # use multiple TMEM buffers to overlap MMA and epilogue tmem_buffers = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_N), tl.float32, NUM_TMEM_BUFFERS, tlx.storage_kind.tmem) # allocate barriers smem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1) smem_full_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1) tmem_full_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1) tmem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1) with tlx.async_tasks(): with tlx.async_task("default"): # epilogue consumer # common code duplicated for each region to avoid SMEM overhead start_pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) num_pid_in_group = GROUP_SIZE_M * num_pid_n num_tiles = num_pid_m * num_pid_n k_tiles = tl.cdiv(K, BLOCK_SIZE_K) # end of common code tmem_read_phase = 0 cur_tmem_buf = 0 for tile_id in range(start_pid, num_tiles, NUM_SMS): pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M) offs_am = pid_m * BLOCK_SIZE_M offs_bn = pid_n * BLOCK_SIZE_N tlx.barrier_wait(tmem_full_bars[cur_tmem_buf], tmem_read_phase) # flip phase at the end of a round of using TMEM barriers tmem_read_phase = tmem_read_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1) # load the result from TMEM to registers acc_tmem = tmem_buffers[cur_tmem_buf] if EPILOGUE_SUBTILE: # We load/store the result half by half to reduce SMEM pressure acc_tmem_subslice1 = tlx.subslice(acc_tmem, 0, BLOCK_SIZE_N // 2) result = tlx.local_load(acc_tmem_subslice1) c = result.to(tl.float16) c_desc.store([offs_am, offs_bn], c) acc_tmem_subslice2 = tlx.subslice(acc_tmem, BLOCK_SIZE_N // 2, BLOCK_SIZE_N // 2) result = tlx.local_load(acc_tmem_subslice2) c = result.to(tl.float16) c_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], c) else: result = tlx.local_load(acc_tmem) c = result.to(tl.float16) c_desc.store([offs_am, offs_bn], c) # done storing this buffer, signal MMA consumer to resume writing to it tlx.barrier_arrive(tmem_empty_bars[cur_tmem_buf], 1) cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS with tlx.async_task(num_warps=1, num_regs=232): # MMA consumer # common code duplicated for each region to avoid SMEM overhead start_pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) num_pid_in_group = GROUP_SIZE_M * num_pid_n num_tiles = num_pid_m * num_pid_n k_tiles = tl.cdiv(K, BLOCK_SIZE_K) # end of common code dot_phase = 0 # the current phase of dot op tmem_write_phase = 1 # sync between epilogue consumer and MMA consumer cur_tmem_buf = 0 processed_k_iters = 0 for tile_id in range(start_pid, num_tiles, NUM_SMS): pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M) offs_am = pid_m * BLOCK_SIZE_M offs_bn = pid_n * BLOCK_SIZE_N # wait epilogue consumer to be done with the buffer before reusing it tlx.barrier_wait(tmem_empty_bars[cur_tmem_buf], tmem_write_phase) # flip phase at the end of a round of using TMEM barriers tmem_write_phase = tmem_write_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1) # now iterate along K to compute result for the block for k in range(0, k_tiles): # processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1 buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS # wait for current phase(round) of load for this buf tlx.barrier_wait(smem_full_bars[buf], dot_phase) # buffer is now ready with loaded data, tlx.async_dot will signal `mBarrier` when done tlx.async_dot( buffers_A[buf], buffers_B[buf], tmem_buffers[cur_tmem_buf], use_acc=k > 0, mBarriers=[smem_empty_bars[buf]], out_dtype=tl.float32, ) # flip phase at the end of a round dot_phase = dot_phase ^ (buf == NUM_SMEM_BUFFERS - 1) # wait for last mma to complete last_buf = (processed_k_iters + k_tiles - 1) % NUM_SMEM_BUFFERS # in case phase was flipped, we should use the phase value when dot op was issued last_dot_phase = dot_phase ^ (last_buf == NUM_SMEM_BUFFERS - 1) tlx.barrier_wait(smem_empty_bars[last_buf], last_dot_phase) # done filling this buffer, signal epilogue consumer tlx.barrier_arrive(tmem_full_bars[cur_tmem_buf], 1) # possibly enter next iteration (next tile) without waiting for epilogue cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS processed_k_iters += k_tiles with tlx.async_task(num_warps=1, num_regs=232): # producer, TMA load # common code duplicated for each region to avoid SMEM overhead start_pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) num_pid_in_group = GROUP_SIZE_M * num_pid_n num_tiles = num_pid_m * num_pid_n k_tiles = tl.cdiv(K, BLOCK_SIZE_K) # end of common code load_phase = 0 # the current phase of TMA load # we virtually "flatten" the two layer loop as if we're performing tma loads on # one big list of data processed_k_iters = 0 for tile_id in range(start_pid, num_tiles, NUM_SMS): pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M) offs_am = pid_m * BLOCK_SIZE_M offs_bn = pid_n * BLOCK_SIZE_N for k in range(0, k_tiles): # processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1 buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS # wait for previous phase(round) of dot for this buf tlx.barrier_wait(smem_empty_bars[buf], load_phase ^ 1) # buffer is now ready to be used again offs_k = k * BLOCK_SIZE_K tlx.barrier_expect_bytes(smem_full_bars[buf], 2 * (BLOCK_SIZE_M + BLOCK_SIZE_N) * BLOCK_SIZE_K) # float16 tlx.async_descriptor_load(a_desc, buffers_A[buf], [offs_am, offs_k], smem_full_bars[buf]) tlx.async_descriptor_load(b_desc, buffers_B[buf], [offs_k, offs_bn], smem_full_bars[buf]) # flip phase at the end of a round load_phase = load_phase ^ (buf == NUM_SMEM_BUFFERS - 1) processed_k_iters += k_tiles
수정본
파일 열기
def matmul_kernel_tma_ws_blackwell_clc(a_desc, b_desc, c_desc, M, N, K, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr, # GROUP_SIZE_M: tl.constexpr, # NUM_SMEM_BUFFERS: tl.constexpr, # NUM_TMEM_BUFFERS: tl.constexpr, # NUM_SMS: tl.constexpr, # NUM_CLC_STAGES: tl.constexpr, # EPILOGUE_SUBTILE: tl.constexpr, # ): # allocate NUM_SMEM_BUFFERS buffers buffers_A = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_K), tl.float16, NUM_SMEM_BUFFERS) buffers_B = tlx.local_alloc((BLOCK_SIZE_K, BLOCK_SIZE_N), tl.float16, NUM_SMEM_BUFFERS) # use multiple TMEM buffers to overlap MMA and epilogue tmem_buffers = tlx.local_alloc((BLOCK_SIZE_M, BLOCK_SIZE_N), tl.float32, NUM_TMEM_BUFFERS, tlx.storage_kind.tmem) # allocate barriers smem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1) smem_full_bars = tlx.alloc_barriers(num_barriers=NUM_SMEM_BUFFERS, arrive_count=1) tmem_full_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1) tmem_empty_bars = tlx.alloc_barriers(num_barriers=NUM_TMEM_BUFFERS, arrive_count=1) clc_context = tlx.clc_create_context(NUM_CLC_STAGES, 3) with tlx.async_tasks(): with tlx.async_task("default"): # epilogue consumer # common code duplicated for each region to avoid SMEM overhead start_pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) num_pid_in_group = GROUP_SIZE_M * num_pid_n k_tiles = tl.cdiv(K, BLOCK_SIZE_K) # end of common code tmem_read_phase = 0 cur_tmem_buf = 0 tile_id = start_pid clc_phase_producer = 1 clc_phase_consumer = 0 clc_buf = 0 while tile_id != -1: clc_buf = clc_buf % NUM_CLC_STAGES # Debug prints # if tlx.thread_id(axis=0) == 0: # tl.device_print("Default WG Processing CtaID", tile_id) # producer tlx.clc_producer(clc_context, clc_buf, clc_phase_producer) # clc_phase_producer ^= 1 clc_phase_producer = clc_phase_producer ^ (clc_buf == (NUM_CLC_STAGES - 1)) pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M) offs_am = pid_m * BLOCK_SIZE_M offs_bn = pid_n * BLOCK_SIZE_N tlx.barrier_wait(tmem_full_bars[cur_tmem_buf], tmem_read_phase) # flip phase at the end of a round of using TMEM barriers tmem_read_phase = tmem_read_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1) # load the result from TMEM to registers acc_tmem = tmem_buffers[cur_tmem_buf] if EPILOGUE_SUBTILE: # We load/store the result half by half to reduce SMEM pressure acc_tmem_subslice1 = tlx.subslice(acc_tmem, 0, BLOCK_SIZE_N // 2) result = tlx.local_load(acc_tmem_subslice1) c = result.to(tl.float16) c_desc.store([offs_am, offs_bn], c) acc_tmem_subslice2 = tlx.subslice(acc_tmem, BLOCK_SIZE_N // 2, BLOCK_SIZE_N // 2) result = tlx.local_load(acc_tmem_subslice2) c = result.to(tl.float16) c_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], c) else: result = tlx.local_load(acc_tmem) c = result.to(tl.float16) c_desc.store([offs_am, offs_bn], c) # done storing this buffer, signal MMA consumer to resume writing to it tlx.barrier_arrive(tmem_empty_bars[cur_tmem_buf], 1) cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS tile_id = tlx.clc_consumer(clc_context, clc_buf, clc_phase_consumer) # clc_phase_consumer ^= 1 clc_phase_consumer = clc_phase_consumer ^ (clc_buf == (NUM_CLC_STAGES - 1)) clc_buf += 1 # Debug-only: verifying that CLC steals workloads successfully # if tlx.thread_id(axis=0) == 0: # tl.device_print("Extracted CtaID", tile_id) with tlx.async_task(num_warps=1, num_regs=232): # MMA consumer # common code duplicated for each region to avoid SMEM overhead start_pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) num_pid_in_group = GROUP_SIZE_M * num_pid_n k_tiles = tl.cdiv(K, BLOCK_SIZE_K) # end of common code dot_phase = 0 # the current phase of dot op tmem_write_phase = 1 # sync between epilogue consumer and MMA consumer cur_tmem_buf = 0 processed_k_iters = 0 tile_id = start_pid clc_phase = 0 clc_buf = 0 while tile_id != -1: clc_buf = clc_buf % NUM_CLC_STAGES pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M) offs_am = pid_m * BLOCK_SIZE_M offs_bn = pid_n * BLOCK_SIZE_N # wait epilogue consumer to be done with the buffer before reusing it tlx.barrier_wait(tmem_empty_bars[cur_tmem_buf], tmem_write_phase) # flip phase at the end of a round of using TMEM barriers tmem_write_phase = tmem_write_phase ^ (cur_tmem_buf == NUM_TMEM_BUFFERS - 1) # now iterate along K to compute result for the block for k in range(0, k_tiles): # processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1 buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS # wait for current phase(round) of load for this buf tlx.barrier_wait(smem_full_bars[buf], dot_phase) # buffer is now ready with loaded data, tlx.async_dot will signal `mBarrier` when done tlx.async_dot(buffers_A[buf], buffers_B[buf], tmem_buffers[cur_tmem_buf], use_acc=k > 0, mBarriers=[smem_empty_bars[buf]], out_dtype=tl.float32) # flip phase at the end of a round dot_phase = dot_phase ^ (buf == NUM_SMEM_BUFFERS - 1) # wait for last mma to complete last_buf = (processed_k_iters + k_tiles - 1) % NUM_SMEM_BUFFERS # in case phase was flipped, we should use the phase value when dot op was issued last_dot_phase = dot_phase ^ (last_buf == NUM_SMEM_BUFFERS - 1) tlx.barrier_wait(smem_empty_bars[last_buf], last_dot_phase) # done filling this buffer, signal epilogue consumer tlx.barrier_arrive(tmem_full_bars[cur_tmem_buf], 1) # possibly enter next iteration (next tile) without waiting for epilogue cur_tmem_buf = (cur_tmem_buf + 1) % NUM_TMEM_BUFFERS processed_k_iters += k_tiles tile_id = tlx.clc_consumer(clc_context, clc_buf, clc_phase) # clc_phase ^= 1 clc_phase = clc_phase ^ (clc_buf == (NUM_CLC_STAGES - 1)) clc_buf += 1 with tlx.async_task(num_warps=1, num_regs=232): # producer, TMA load # common code duplicated for each region to avoid SMEM overhead start_pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) num_pid_in_group = GROUP_SIZE_M * num_pid_n k_tiles = tl.cdiv(K, BLOCK_SIZE_K) # end of common code load_phase = 0 # the current phase of TMA load # we virtually "flatten" the two layer loop as if we're performing tma loads on # one big list of data processed_k_iters = 0 tile_id = start_pid clc_phase = 0 clc_buf = 0 while tile_id != -1: clc_buf = clc_buf % NUM_CLC_STAGES pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M) offs_am = pid_m * BLOCK_SIZE_M offs_bn = pid_n * BLOCK_SIZE_N for k in range(0, k_tiles): # processed_k_iters + k means we use the immediate next buffer slot of tile_id x when we start tile_id x+1 buf = (processed_k_iters + k) % NUM_SMEM_BUFFERS # wait for previous phase(round) of dot for this buf tlx.barrier_wait(smem_empty_bars[buf], load_phase ^ 1) # buffer is now ready to be used again offs_k = k * BLOCK_SIZE_K tlx.barrier_expect_bytes(smem_full_bars[buf], 2 * (BLOCK_SIZE_M + BLOCK_SIZE_N) * BLOCK_SIZE_K) # float16 tlx.async_descriptor_load(a_desc, buffers_A[buf], [offs_am, offs_k], smem_full_bars[buf]) tlx.async_descriptor_load(b_desc, buffers_B[buf], [offs_k, offs_bn], smem_full_bars[buf]) # flip phase at the end of a round load_phase = load_phase ^ (buf == NUM_SMEM_BUFFERS - 1) processed_k_iters += k_tiles tile_id = tlx.clc_consumer(clc_context, clc_buf, clc_phase) # clc_phase ^= 1 clc_phase = clc_phase ^ (clc_buf == (NUM_CLC_STAGES - 1)) clc_buf += 1
비교하기