submission 408556
oofbaroomf · python · License unknown
Use it
Vendorable · source mirrored · license unknownView source →
No package. Vendor the mirrored source: 1959 lines, June 9 Researcher Reciprocity License v1.0.
submission.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-nvfp4-group-gemm-408556?include=source"interfacepython
Compatibility
measured onNVIDIA B200
declared hardwareNVIDIA B200
architecturessm_100
dtypesfp8_e4m3, nvfp4
Benchmark evidence
1 measurement across 1 GPU, fastest first.
Operation / workload
Hardware
Latency
Rank
Observed
Reported · How evidence levels are derived →
Source and license
sourceavailable
revision digestsha256:547cef53324123981fc810f86109d942a471ba42f9adcee2969d8270e76174e7
license declaredunknown
license concludedunknown
authorsoofbaroomf
imported2026-08-15
Techniques
Extracted from the mirrored source by pattern, never inferred. Each row cites its line.
fp4
tiled_mma, # Tiled MMA object defining NVFP4 GEMM compute patternmbarrier
tmem_alloc_barrier = pipeline.NamedBarrier(shared-memory
a_smem_layout_staged: cute.ComposedLayout,tcgen05
acc_tmem_ptr + tcgen05.find_tmem_tensor_col_offset(tCtAcc),warp-specialization
ab_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)Kernel source
submission.py1959 lines
import base64
import os
import torch
from task import input_t, output_t
import cutlass
import cutlass.cute as cute
import cutlass.utils as utils
import cutlass.pipeline as pipeline
from cutlass.cute.nvgpu import cpasync, tcgen05
import cutlass.torch as cutlass_torch
import cutlass.utils.blackwell_helpers as sm100_utils
import cutlass.utils.blockscaled_layout as blockscaled_utils
from cutlass.cute.runtime import make_ptr
# Kernel configuration parameters
# Tile sizes for M, N, K dimensions
mma_tiler_mnk = (128, 128, 256)
# Shape of the K dimension for the MMA instruction
mma_inst_shape_k = 64
# FP4 data type for A and B
ab_dtype = cutlass.Float4E2M1FN
# FP8 data type for scale factors
sf_dtype = cutlass.Float8E4M3FN
# FP16 output type
c_dtype = cutlass.Float16
# Scale factor block size (16 elements share one scale)
sf_vec_size = 16
# Number of threads per CUDA thread block
threads_per_cta = 128
# Stage numbers of shared memory and tmem
num_acc_stage = 1
num_ab_stage = 1
# Total number of columns in tmem
num_tmem_alloc_cols = 512
# Helper function for ceiling division
def ceil_div(a, b):
return (a + b - 1) // b
# The CuTe reference implementation for NVFP4 block-scaled GEMM
@cute.kernel
def kernel(
tiled_mma: cute.TiledMma,
tma_atom_a: cute.CopyAtom,
mA_mkl: cute.Tensor,
tma_atom_b: cute.CopyAtom,
mB_nkl: cute.Tensor,
tma_atom_sfa: cute.CopyAtom,
mSFA_mkl: cute.Tensor,
tma_atom_sfb: cute.CopyAtom,
mSFB_nkl: cute.Tensor,
mC_mnl: cute.Tensor,
a_smem_layout_staged: cute.ComposedLayout,
b_smem_layout_staged: cute.ComposedLayout,
sfa_smem_layout_staged: cute.Layout,
sfb_smem_layout_staged: cute.Layout,
num_tma_load_bytes: cutlass.Constexpr[int],
):
"""
GPU device kernel performing the batched GEMM computation.
"""
warp_idx = cute.arch.warp_idx()
warp_idx = cute.arch.make_warp_uniform(warp_idx)
tidx = cute.arch.thread_idx()
#
# Setup cta/thread coordinates
#
# Coords inside cluster
bidx, bidy, bidz = cute.arch.block_idx()
# Coords outside cluster
cta_coord = (bidx, bidy, bidz)
mma_tile_coord_mnl = (
cta_coord[0] // cute.size(tiled_mma.thr_id.shape),
cta_coord[1],
cta_coord[2],
)
# Coord inside cta
tidx, _, _ = cute.arch.thread_idx()
#
# Define shared storage for kernel
#
@cute.struct
class SharedStorage:
ab_mbar_ptr: cute.struct.MemRange[cutlass.Int64, num_ab_stage * 2]
acc_mbar_ptr: cute.struct.MemRange[cutlass.Int64, num_acc_stage * 2]
tmem_holding_buf: cutlass.Int32
smem = utils.SmemAllocator()
storage = smem.allocate(SharedStorage)
# (MMA, MMA_M, MMA_K, STAGE)
sA = smem.allocate_tensor(
element_type=ab_dtype,
layout=a_smem_layout_staged.outer,
byte_alignment=128,
swizzle=a_smem_layout_staged.inner,
)
# (MMA, MMA_N, MMA_K, STAGE)
sB = smem.allocate_tensor(
element_type=ab_dtype,
layout=b_smem_layout_staged.outer,
byte_alignment=128,
swizzle=b_smem_layout_staged.inner,
)
# (MMA, MMA_M, MMA_K, STAGE)
sSFA = smem.allocate_tensor(
element_type=sf_dtype,
layout=sfa_smem_layout_staged,
byte_alignment=128,
)
# (MMA, MMA_N, MMA_K, STAGE)
sSFB = smem.allocate_tensor(
element_type=sf_dtype,
layout=sfb_smem_layout_staged,
byte_alignment=128,
)
#
# Initialize mainloop ab_pipeline, acc_pipeline and their states
#
ab_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
ab_pipeline_consumer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread, 1)
ab_producer, ab_consumer = pipeline.PipelineTmaUmma.create(
barrier_storage=storage.ab_mbar_ptr.data_ptr(),
num_stages=num_ab_stage,
producer_group=ab_pipeline_producer_group,
consumer_group=ab_pipeline_consumer_group,
tx_count=num_tma_load_bytes,
).make_participants()
acc_producer, acc_consumer = pipeline.PipelineUmmaAsync.create(
barrier_storage=storage.acc_mbar_ptr.data_ptr(),
num_stages=num_acc_stage,
producer_group=ab_pipeline_producer_group,
consumer_group=pipeline.CooperativeGroup(
pipeline.Agent.Thread,
threads_per_cta,
),
).make_participants()
#
# Local_tile partition global tensors
#
# (bM, bK, RestM, RestK, RestL)
gA_mkl = cute.local_tile(
mA_mkl, cute.slice_(mma_tiler_mnk, (None, 0, None)), (None, None, None)
)
# (bN, bK, RestN, RestK, RestL)
gB_nkl = cute.local_tile(
mB_nkl, cute.slice_(mma_tiler_mnk, (0, None, None)), (None, None, None)
)
gSFA_mkl = cute.local_tile(
mSFA_mkl, cute.slice_(mma_tiler_mnk, (None, 0, None)), (None, None, None)
)
gSFB_nkl = cute.local_tile(
mSFB_nkl, cute.slice_(mma_tiler_mnk, (0, None, None)), (None, None, None)
)
# (bM, bN, RestM, RestN, RestL)
gC_mnl = cute.local_tile(
mC_mnl, cute.slice_(mma_tiler_mnk, (None, None, 0)), (None, None, None)
)
k_tile_cnt = cute.size(gA_mkl, mode=[3])
#
# Partition global tensor for TiledMMA_A/B/SFA/SFB/C
#
# (MMA, MMA_M, MMA_K, RestK)
thr_mma = tiled_mma.get_slice(0)
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
tCgA = thr_mma.partition_A(gA_mkl)
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
tCgB = thr_mma.partition_B(gB_nkl)
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
tCgSFA = thr_mma.partition_A(gSFA_mkl)
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
tCgSFB = thr_mma.partition_B(gSFB_nkl)
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
tCgC = thr_mma.partition_C(gC_mnl)
#
# Partition global/shared tensor for TMA load A/B/SFA/SFB
#
# TMA Partition_S/D for A
# ((atom_v, rest_v), STAGE)
# ((atom_v, rest_v), RestM, RestK, RestL)
tAsA, tAgA = cpasync.tma_partition(
tma_atom_a,
0,
cute.make_layout(1),
cute.group_modes(sA, 0, 3),
cute.group_modes(tCgA, 0, 3),
)
# TMA Partition_S/D for B
# ((atom_v, rest_v), STAGE)
# ((atom_v, rest_v), RestN, RestK, RestL)
tBsB, tBgB = cpasync.tma_partition(
tma_atom_b,
0,
cute.make_layout(1),
cute.group_modes(sB, 0, 3),
cute.group_modes(tCgB, 0, 3),
)
# TMA Partition_S/D for SFA
# ((atom_v, rest_v), STAGE)
# ((atom_v, rest_v), RestM, RestK, RestL)
tAsSFA, tAgSFA = cpasync.tma_partition(
tma_atom_sfa,
0,
cute.make_layout(1),
cute.group_modes(sSFA, 0, 3),
cute.group_modes(tCgSFA, 0, 3),
)
tAsSFA = cute.filter_zeros(tAsSFA)
tAgSFA = cute.filter_zeros(tAgSFA)
# TMA Partition_S/D for SFB
# ((atom_v, rest_v), STAGE)
# ((atom_v, rest_v), RestN, RestK, RestL)
tBsSFB, tBgSFB = cpasync.tma_partition(
tma_atom_sfb,
0,
cute.make_layout(1),
cute.group_modes(sSFB, 0, 3),
cute.group_modes(tCgSFB, 0, 3),
)
tBsSFB = cute.filter_zeros(tBsSFB)
tBgSFB = cute.filter_zeros(tBgSFB)
#
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
#
# (MMA, MMA_M, MMA_K, STAGE)
tCrA = tiled_mma.make_fragment_A(sA)
# (MMA, MMA_N, MMA_K, STAGE)
tCrB = tiled_mma.make_fragment_B(sB)
# (MMA, MMA_M, MMA_N)
acc_shape = tiled_mma.partition_shape_C(mma_tiler_mnk[:2])
# (MMA, MMA_M, MMA_N)
tCtAcc_fake = tiled_mma.make_fragment_C(acc_shape)
#
# Alloc tensor memory buffer
#
tmem_alloc_barrier = pipeline.NamedBarrier(
barrier_id=1,
num_threads=threads_per_cta,
)
tmem = utils.TmemAllocator(
storage.tmem_holding_buf,
barrier_for_retrieve=tmem_alloc_barrier,
)
tmem.allocate(num_tmem_alloc_cols)
tmem.wait_for_alloc()
acc_tmem_ptr = tmem.retrieve_ptr(cutlass.Float32)
tCtAcc = cute.make_tensor(acc_tmem_ptr, tCtAcc_fake.layout)
#
# Make SFA/SFB tmem tensor
#
# Get SFA tmem ptr
sfa_tmem_ptr = cute.recast_ptr(
acc_tmem_ptr + tcgen05.find_tmem_tensor_col_offset(tCtAcc),
dtype=sf_dtype,
)
# (MMA, MMA_M, MMA_K)
tCtSFA_layout = blockscaled_utils.make_tmem_layout_sfa(
tiled_mma,
mma_tiler_mnk,
sf_vec_size,
cute.slice_(sfa_smem_layout_staged, (None, None, None, 0)),
)
tCtSFA = cute.make_tensor(sfa_tmem_ptr, tCtSFA_layout)
# Get SFB tmem ptr
sfb_tmem_ptr = cute.recast_ptr(
acc_tmem_ptr
+ tcgen05.find_tmem_tensor_col_offset(tCtAcc)
+ tcgen05.find_tmem_tensor_col_offset(tCtSFA),
dtype=sf_dtype,
)
# (MMA, MMA_N, MMA_K)
tCtSFB_layout = blockscaled_utils.make_tmem_layout_sfb(
tiled_mma,
mma_tiler_mnk,
sf_vec_size,
cute.slice_(sfb_smem_layout_staged, (None, None, None, 0)),
)
tCtSFB = cute.make_tensor(sfb_tmem_ptr, tCtSFB_layout)
#
# Partition for S2T copy of SFA/SFB
#
# Make S2T CopyAtom
copy_atom_s2t = cute.make_copy_atom(
tcgen05.Cp4x32x128bOp(tcgen05.CtaGroup.ONE),
sf_dtype,
)
# (MMA, MMA_MN, MMA_K, STAGE)
tCsSFA_compact = cute.filter_zeros(sSFA)
# (MMA, MMA_MN, MMA_K)
tCtSFA_compact = cute.filter_zeros(tCtSFA)
tiled_copy_s2t_sfa = tcgen05.make_s2t_copy(copy_atom_s2t, tCtSFA_compact)
thr_copy_s2t_sfa = tiled_copy_s2t_sfa.get_slice(0)
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
tCsSFA_compact_s2t_ = thr_copy_s2t_sfa.partition_S(tCsSFA_compact)
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
tCsSFA_compact_s2t = tcgen05.get_s2t_smem_desc_tensor(
tiled_copy_s2t_sfa, tCsSFA_compact_s2t_
)
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K)
tCtSFA_compact_s2t = thr_copy_s2t_sfa.partition_D(tCtSFA_compact)
# (MMA, MMA_MN, MMA_K, STAGE)
tCsSFB_compact = cute.filter_zeros(sSFB)
# (MMA, MMA_MN, MMA_K)
tCtSFB_compact = cute.filter_zeros(tCtSFB)
tiled_copy_s2t_sfb = tcgen05.make_s2t_copy(copy_atom_s2t, tCtSFB_compact)
thr_copy_s2t_sfb = tiled_copy_s2t_sfb.get_slice(0)
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
tCsSFB_compact_s2t_ = thr_copy_s2t_sfb.partition_S(tCsSFB_compact)
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
tCsSFB_compact_s2t = tcgen05.get_s2t_smem_desc_tensor(
tiled_copy_s2t_sfb, tCsSFB_compact_s2t_
)
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K)
tCtSFB_compact_s2t = thr_copy_s2t_sfb.partition_D(tCtSFB_compact)
#
# Slice to per mma tile index
#
# ((atom_v, rest_v), RestK)
tAgA = tAgA[(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])]
# ((atom_v, rest_v), RestK)
tBgB = tBgB[(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])]
# ((atom_v, rest_v), RestK)
tAgSFA = tAgSFA[(None, mma_tile_coord_mnl[0], None, mma_tile_coord_mnl[2])]
# ((atom_v, rest_v), RestK)
tBgSFB = tBgSFB[(None, mma_tile_coord_mnl[1], None, mma_tile_coord_mnl[2])]
#
# Execute Data copy and Math computation in the k_tile loop
#
if warp_idx == 0:
# Wait for accumulator buffer empty
acc_empty = acc_producer.acquire_and_advance()
# Set ACCUMULATE field to False for the first k_tile iteration
tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
# Execute k_tile loop
for k_tile in range(k_tile_cnt):
# Wait for AB buffer empty
ab_empty = ab_producer.acquire_and_advance()
# TMA load A/B/SFA/SFB to shared memory
cute.copy(
tma_atom_a,
tAgA[(None, k_tile)],
tAsA[(None, ab_empty.index)],
tma_bar_ptr=ab_empty.barrier,
)
cute.copy(
tma_atom_b,
tBgB[(None, k_tile)],
tBsB[(None, ab_empty.index)],
tma_bar_ptr=ab_empty.barrier,
)
cute.copy(
tma_atom_sfa,
tAgSFA[(None, k_tile)],
tAsSFA[(None, ab_empty.index)],
tma_bar_ptr=ab_empty.barrier,
)
cute.copy(
tma_atom_sfb,
tBgSFB[(None, k_tile)],
tBsSFB[(None, ab_empty.index)],
tma_bar_ptr=ab_empty.barrier,
)
# Wait for AB buffer full
ab_full = ab_consumer.wait_and_advance()
# Copy SFA/SFB from shared memory to TMEM
s2t_stage_coord = (None, None, None, None, ab_full.index)
tCsSFA_compact_s2t_staged = tCsSFA_compact_s2t[s2t_stage_coord]
tCsSFB_compact_s2t_staged = tCsSFB_compact_s2t[s2t_stage_coord]
cute.copy(
tiled_copy_s2t_sfa,
tCsSFA_compact_s2t_staged,
tCtSFA_compact_s2t,
)
cute.copy(
tiled_copy_s2t_sfb,
tCsSFB_compact_s2t_staged,
tCtSFB_compact_s2t,
)
# tCtAcc += tCrA * tCrSFA * tCrB * tCrSFB
num_kblocks = cute.size(tCrA, mode=[2])
for kblock_idx in cutlass.range(num_kblocks, unroll_full=True):
kblock_coord = (
None,
None,
kblock_idx,
ab_full.index,
)
# Set SFA/SFB tensor to tiled_mma
sf_kblock_coord = (None, None, kblock_idx)
tiled_mma.set(
tcgen05.Field.SFA,
tCtSFA[sf_kblock_coord].iterator,
)
tiled_mma.set(
tcgen05.Field.SFB,
tCtSFB[sf_kblock_coord].iterator,
)
cute.gemm(
tiled_mma,
tCtAcc,
tCrA[kblock_coord],
tCrB[kblock_coord],
tCtAcc,
)
# Enable accumulate on tCtAcc after first kblock
tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
# Async arrive AB buffer empty
ab_full.release()
acc_empty.commit()
#
# Epilogue
# Partition for epilogue
#
op = tcgen05.Ld32x32bOp(tcgen05.Repetition.x128, tcgen05.Pack.NONE)
copy_atom_t2r = cute.make_copy_atom(op, cutlass.Float32)
tiled_copy_t2r = tcgen05.make_tmem_copy(copy_atom_t2r, tCtAcc)
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
# (T2R_M, T2R_N, EPI_M, EPI_M)
tTR_tAcc = thr_copy_t2r.partition_S(tCtAcc)
# (T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
tTR_gC = thr_copy_t2r.partition_D(tCgC)
# (T2R_M, T2R_N, EPI_M, EPI_N)
tTR_rAcc = cute.make_rmem_tensor(
tTR_gC[None, None, None, None, 0, 0, 0].shape, cutlass.Float32
)
# (T2R_M, T2R_N, EPI_M, EPI_N)
tTR_rC = cute.make_rmem_tensor(
tTR_gC[None, None, None, None, 0, 0, 0].shape, c_dtype
)
# STG Atom
simt_atom = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), c_dtype)
tTR_gC = tTR_gC[(None, None, None, None, *mma_tile_coord_mnl)]
# Wait for accumulator buffer full
acc_full = acc_consumer.wait_and_advance()
# Copy accumulator to register
cute.copy(tiled_copy_t2r, tTR_tAcc, tTR_rAcc)
acc_vec = tTR_rAcc.load().to(c_dtype)
tTR_rC.store(acc_vec)
# Store C to global memory
cute.copy(simt_atom, tTR_rC, tTR_gC)
acc_full.release()
# Deallocate TMEM
cute.arch.barrier()
tmem.free(acc_tmem_ptr)
return
@cute.jit
def my_kernel(
a_ptr: cute.Pointer,
b_ptr: cute.Pointer,
sfa_ptr: cute.Pointer,
sfb_ptr: cute.Pointer,
c_ptr: cute.Pointer,
problem_size: tuple,
):
"""
Host-side JIT function to prepare tensors and launch GPU kernel.
"""
m, n, k, l = problem_size
# Setup attributes that depend on gemm inputs
a_tensor = cute.make_tensor(
a_ptr,
cute.make_layout(
(m, cute.assume(k, 32), l),
stride=(cute.assume(k, 32), 1, cute.assume(m * k, 32)),
),
)
b_tensor = cute.make_tensor(
b_ptr,
cute.make_layout(
(n, cute.assume(k, 32), l),
stride=(cute.assume(k, 32), 1, cute.assume(n * k, 32)),
),
)
c_tensor = cute.make_tensor(
c_ptr, cute.make_layout((cute.assume(m, 32), n, l), stride=(n, 1, m * n))
)
# Setup sfa/sfb tensor by filling A/B tensor to scale factor atom layout
# ((Atom_M, Rest_M),(Atom_K, Rest_K),RestL)
sfa_layout = blockscaled_utils.tile_atom_to_shape_SF(
a_tensor.shape, sf_vec_size
)
sfa_tensor = cute.make_tensor(sfa_ptr, sfa_layout)
# ((Atom_N, Rest_N),(Atom_K, Rest_K),RestL)
sfb_layout = blockscaled_utils.tile_atom_to_shape_SF(
b_tensor.shape, sf_vec_size
)
sfb_tensor = cute.make_tensor(sfb_ptr, sfb_layout)
mma_op = tcgen05.MmaMXF4NVF4Op(
sf_dtype,
(mma_tiler_mnk[0], mma_tiler_mnk[1], mma_inst_shape_k),
tcgen05.CtaGroup.ONE,
tcgen05.OperandSource.SMEM,
)
tiled_mma = cute.make_tiled_mma(mma_op)
cluster_layout_vmnk = cute.tiled_divide(
cute.make_layout((1, 1, 1)),
(tiled_mma.thr_id.shape,),
)
# Compute A/B/SFA/SFB/C shared memory layout
a_smem_layout_staged = sm100_utils.make_smem_layout_a(
tiled_mma,
mma_tiler_mnk,
ab_dtype,
num_ab_stage,
)
b_smem_layout_staged = sm100_utils.make_smem_layout_b(
tiled_mma,
mma_tiler_mnk,
ab_dtype,
num_ab_stage,
)
sfa_smem_layout_staged = blockscaled_utils.make_smem_layout_sfa(
tiled_mma,
mma_tiler_mnk,
sf_vec_size,
num_ab_stage,
)
sfb_smem_layout_staged = blockscaled_utils.make_smem_layout_sfb(
tiled_mma,
mma_tiler_mnk,
sf_vec_size,
num_ab_stage,
)
atom_thr_size = cute.size(tiled_mma.thr_id.shape)
# Setup TMA for A
a_smem_layout = cute.slice_(a_smem_layout_staged, (None, None, None, 0))
tma_atom_a, tma_tensor_a = cute.nvgpu.make_tiled_tma_atom_A(
cpasync.CopyBulkTensorTileG2SOp(tcgen05.CtaGroup.ONE),
a_tensor,
a_smem_layout,
mma_tiler_mnk,
tiled_mma,
cluster_layout_vmnk.shape,
)
# Setup TMA for B
b_smem_layout = cute.slice_(b_smem_layout_staged, (None, None, None, 0))
tma_atom_b, tma_tensor_b = cute.nvgpu.make_tiled_tma_atom_B(
cpasync.CopyBulkTensorTileG2SOp(tcgen05.CtaGroup.ONE),
b_tensor,
b_smem_layout,
mma_tiler_mnk,
tiled_mma,
cluster_layout_vmnk.shape,
)
# Setup TMA for SFA
sfa_smem_layout = cute.slice_(
sfa_smem_layout_staged, (None, None, None, 0)
)
tma_atom_sfa, tma_tensor_sfa = cute.nvgpu.make_tiled_tma_atom_A(
cpasync.CopyBulkTensorTileG2SOp(tcgen05.CtaGroup.ONE),
sfa_tensor,
sfa_smem_layout,
mma_tiler_mnk,
tiled_mma,
cluster_layout_vmnk.shape,
internal_type=cutlass.Int16,
)
# Setup TMA for SFB
sfb_smem_layout = cute.slice_(
sfb_smem_layout_staged, (None, None, None, 0)
)
tma_atom_sfb, tma_tensor_sfb = cute.nvgpu.make_tiled_tma_atom_B(
cpasync.CopyBulkTensorTileG2SOp(tcgen05.CtaGroup.ONE),
sfb_tensor,
sfb_smem_layout,
mma_tiler_mnk,
tiled_mma,
cluster_layout_vmnk.shape,
internal_type=cutlass.Int16,
)
# Compute TMA load bytes
a_copy_size = cute.size_in_bytes(ab_dtype, a_smem_layout)
b_copy_size = cute.size_in_bytes(ab_dtype, b_smem_layout)
sfa_copy_size = cute.size_in_bytes(sf_dtype, sfa_smem_layout)
sfb_copy_size = cute.size_in_bytes(sf_dtype, sfb_smem_layout)
num_tma_load_bytes = (
a_copy_size + b_copy_size + sfa_copy_size + sfb_copy_size
) * atom_thr_size
# Compute grid size
grid = (
cute.ceil_div(c_tensor.shape[0], mma_tiler_mnk[0]),
cute.ceil_div(c_tensor.shape[1], mma_tiler_mnk[1]),
c_tensor.shape[2],
)
# Launch the kernel
kernel(
# MMA (Matrix Multiply-Accumulate) configuration
tiled_mma, # Tiled MMA object defining NVFP4 GEMM compute pattern
# TMA (Tensor Memory Accelerator) atoms and tensors for input matrix A
tma_atom_a, # TMA copy atom defining how to load A from global memory
tma_tensor_a, # Tensor descriptor for A matrix (m, k, l)
# TMA atoms and tensors for input matrix B
tma_atom_b, # TMA copy atom defining how to load B from global memory
tma_tensor_b, # Tensor descriptor for B matrix (n, k, l)
# TMA atoms and tensors for scale factor A
tma_atom_sfa, # TMA copy atom for loading scale factors for A
tma_tensor_sfa, # Tensor descriptor for SFA (block scale factors for A)
# TMA atoms and tensors for scale factor B
tma_atom_sfb, # TMA copy atom for loading scale factors for B
tma_tensor_sfb, # Tensor descriptor for SFB (block scale factors for B)
# Output tensor C
c_tensor, # Output tensor C where result will be stored (m, n, l)
# Shared memory layouts with staging for pipelined execution
a_smem_layout_staged, # Staged shared memory layout for A (includes stage dimension)
b_smem_layout_staged, # Staged shared memory layout for B (includes stage dimension)
sfa_smem_layout_staged, # Staged shared memory layout for SFA (includes stage dimension)
sfb_smem_layout_staged, # Staged shared memory layout for SFB (includes stage dimension)
# Pipeline synchronization parameter
num_tma_load_bytes, # Total bytes to load per TMA transaction (for barrier setup)
).launch(
grid=grid,
block=[threads_per_cta, 1, 1],
cluster=(1, 1, 1),
)
return
# Global cache for compiled kernel
_compiled_kernel_cache = None
# This function is used to compile the kernel once and cache it and then allow users to
# run the kernel multiple times to get more accurate timing results.
def compile_kernel():
"""
Compile the kernel once and cache it.
This should be called before any timing measurements.
Returns:
The compiled kernel function
"""
global _compiled_kernel_cache
if _compiled_kernel_cache is not None:
return _compiled_kernel_cache
# Create CuTe pointers for A/B/C/SFA/SFB via torch tensor data pointer
a_ptr = make_ptr(
ab_dtype, 0, cute.AddressSpace.gmem, assumed_align=16
)
b_ptr = make_ptr(
ab_dtype, 0, cute.AddressSpace.gmem, assumed_align=16
)
c_ptr = make_ptr(
c_dtype, 0, cute.AddressSpace.gmem, assumed_align=16
)
sfa_ptr = make_ptr(
sf_dtype, 0, cute.AddressSpace.gmem, assumed_align=32
)
sfb_ptr = make_ptr(
sf_dtype, 0, cute.AddressSpace.gmem, assumed_align=32
)
# Compile the kernel
_compiled_kernel_cache = cute.compile(my_kernel, a_ptr, b_ptr, sfa_ptr, sfb_ptr, c_ptr, (0, 0, 0, 0))
return _compiled_kernel_cache
try:
torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = True
torch.backends.cuda.matmul.allow_bf16_reduced_precision_reduction = True
except Exception:
pass
_CPP_B64 = "CiAgICAjaW5jbHVkZSA8dG9yY2gvZXh0ZW5zaW9uLmg+CiAgICAjaW5jbHVkZSA8dmVjdG9yPgoKICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IGdyb3VwZWRfZ2VtbV8xc20oCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKTsKCiAgICBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiBncm91cGVkX2dlbW1fMnNtKAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBBLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBCLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBDLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBTRkEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQik7CiAgICA="
_CU_B64 = "CiAgICAjaW5jbHVkZSA8dG9yY2gvZXh0ZW5zaW9uLmg+CiAgICAjaW5jbHVkZSA8QVRlbi9jdWRhL0NVREFDb250ZXh0Lmg+CiAgICAjaW5jbHVkZSA8YzEwL2N1ZGEvQ1VEQVN0cmVhbS5oPgogICAgI2luY2x1ZGUgPGMxMC9jdWRhL0NVREFHdWFyZC5oPgogICAgI2luY2x1ZGUgPHZlY3Rvcj4KICAgICNpbmNsdWRlIDxzdGRleGNlcHQ+CgogICAgI2lmZGVmIF9fQ1VEQV9OT19IQUxGX09QRVJBVE9SU19fCiAgICAjdW5kZWYgX19DVURBX05PX0hBTEZfT1BFUkFUT1JTX18KICAgICNlbmRpZgogICAgI2lmZGVmIF9fQ1VEQV9OT19IQUxGX0NPTlZFUlNJT05TX18KICAgICN1bmRlZiBfX0NVREFfTk9fSEFMRl9DT05WRVJTSU9OU19fCiAgICAjZW5kaWYKICAgICNpZmRlZiBfX0NVREFfTk9fSEFMRjJfT1BFUkFUT1JTX18KICAgICN1bmRlZiBfX0NVREFfTk9fSEFMRjJfT1BFUkFUT1JTX18KICAgICNlbmRpZgoKICAgICNpbmNsdWRlIDxjdWRhX3J1bnRpbWUuaD4KICAgICNpZiBkZWZpbmVkKF9fQ1VEQV9BUkNIX18pICYmICFkZWZpbmVkKENVVExBU1NfQVJDSF9NTUFfU00xMDBBX0VOQUJMRUQpCiAgICAjZGVmaW5lIENVVExBU1NfQVJDSF9NTUFfU00xMDBBX0VOQUJMRUQgMQogICAgI2VuZGlmCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9jdXRsYXNzLmgiCiAgICAjaW5jbHVkZSAiY3V0ZS90ZW5zb3IuaHBwIgogICAgI2luY2x1ZGUgImN1dGxhc3MvdGVuc29yX3JlZi5oIgogICAgI2luY2x1ZGUgImN1dGxhc3MvZXBpbG9ndWUvY29sbGVjdGl2ZS9kZWZhdWx0X2VwaWxvZ3VlLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2VwaWxvZ3VlL3RocmVhZC9saW5lYXJfY29tYmluYXRpb24uaCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZGlzcGF0Y2hfcG9saWN5LmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZ3JvdXBfYXJyYXlfcHJvYmxlbV9zaGFwZS5ocHAiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9nZW1tL2NvbGxlY3RpdmUvY29sbGVjdGl2ZV9idWlsZGVyLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2VwaWxvZ3VlL2NvbGxlY3RpdmUvY29sbGVjdGl2ZV9idWlsZGVyLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZGV2aWNlL2dlbW1fdW5pdmVyc2FsX2FkYXB0ZXIuaCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0va2VybmVsL2dlbW1fdW5pdmVyc2FsLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL3V0aWwvcGFja2VkX3N0cmlkZS5ocHAiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9rZXJuZWxfaGFyZHdhcmVfaW5mby5oIgogICAgI2luY2x1ZGUgImN1dGxhc3MvdXRpbC9kZXZpY2VfbWVtb3J5LmgiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9nZW1tL2tlcm5lbC90aWxlX3NjaGVkdWxlcl9wYXJhbXMuaCIKCiAgICAjZGVmaW5lIENVVExBU1NfQ0hFQ0soc3RhdHVzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICBkbyB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICBjdXRsYXNzOjpTdGF0dXMgX3N0YXR1cyA9IChzdGF0dXMpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgICAgaWYgKF9zdGF0dXMgIT0gY3V0bGFzczo6U3RhdHVzOjprU3VjY2VzcykgeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICAgICAgdGhyb3cgc3RkOjpydW50aW1lX2Vycm9yKCJDVVRMQVNTIGVycm9yIik7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgIH0gd2hpbGUgKDApCgogICAgI2RlZmluZSBDVURBX0NIRUNLKGV4cHIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICBkbyB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICBjdWRhRXJyb3JfdCBfZXJyID0gKGV4cHIpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgICAgaWYgKF9lcnIgIT0gY3VkYVN1Y2Nlc3MpIHsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICAgICAgdGhyb3cgc3RkOjpydW50aW1lX2Vycm9yKGN1ZGFHZXRFcnJvclN0cmluZyhfZXJyKSk7ICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgIH0gd2hpbGUgKDApCgogICAgdXNpbmcgUHJvYmxlbVNoYXBlID0gY3V0bGFzczo6Z2VtbTo6R3JvdXBQcm9ibGVtU2hhcGU8Y3V0ZTo6U2hhcGU8aW50LGludCxpbnQ+PjsKICAgIHVzaW5nIEVsZW1lbnRJbnB1dCA9IGN1dGxhc3M6OmZsb2F0X2UybTFfdDsKICAgIHVzaW5nIEVsZW1lbnRTRiAgICA9IGN1dGxhc3M6OmZsb2F0X3VlNG0zX3Q7CiAgICB1c2luZyBFbGVtZW50QyAgICAgPSBjdXRsYXNzOjpoYWxmX3Q7CgogICAgdXNpbmcgRWxlbWVudEEgPSBjdXRsYXNzOjpudl9mbG9hdDRfdDxFbGVtZW50SW5wdXQ+OwogICAgdXNpbmcgTGF5b3V0QSAgPSBjdXRsYXNzOjpsYXlvdXQ6OlJvd01ham9yOwogICAgY29uc3RleHByIGludCBBbGlnbm1lbnRBICA9IDMyOwoKICAgIHVzaW5nIEVsZW1lbnRCID0gY3V0bGFzczo6bnZfZmxvYXQ0X3Q8RWxlbWVudElucHV0PjsKICAgIHVzaW5nIExheW91dEIgID0gY3V0bGFzczo6bGF5b3V0OjpDb2x1bW5NYWpvcjsKICAgIGNvbnN0ZXhwciBpbnQgQWxpZ25tZW50QiAgPSAzMjsKCiAgICB1c2luZyBFbGVtZW50RCA9IEVsZW1lbnRDOwogICAgdXNpbmcgTGF5b3V0QyAgPSBjdXRsYXNzOjpsYXlvdXQ6OlJvd01ham9yOwogICAgY29uc3RleHByIGludCBBbGlnbm1lbnRDICA9IDEyOCAvIGN1dGxhc3M6OnNpemVvZl9iaXRzPEVsZW1lbnRDPjo6dmFsdWU7CiAgICBjb25zdGV4cHIgaW50IEFsaWdubWVudEQgID0gMTI4IC8gY3V0bGFzczo6c2l6ZW9mX2JpdHM8RWxlbWVudEQ+Ojp2YWx1ZTsKICAgIHVzaW5nIEVsZW1lbnRBY2N1bXVsYXRvciAgPSBmbG9hdDsKCiAgICB1c2luZyBBcmNoVGFnID0gY3V0bGFzczo6YXJjaDo6U20xMDA7CiAgICB1c2luZyBPcGVyYXRvckNsYXNzID0gY3V0bGFzczo6YXJjaDo6T3BDbGFzc0Jsb2NrU2NhbGVkVGVuc29yT3A7CiAgICB1c2luZyBTdGFnZUNvdW50VHlwZSA9IGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OlN0YWdlQ291bnRBdXRvOwogICAgdXNpbmcgQ2x1c3RlclNoYXBlID0gY3V0ZTo6U2hhcGU8aW50MzJfdCxpbnQzMl90LGN1dGU6Ol8xPjsKCiAgICBzdHJ1Y3QgTU1BMVNNQ29uZmlnIHsKICAgICAgdXNpbmcgTW1hVGlsZVNoYXBlICAgICA9IGN1dGU6OlNoYXBlPGN1dGU6Ol8xMjgsY3V0ZTo6XzI1NixjdXRlOjpfMjU2PjsKICAgICAgdXNpbmcgS2VybmVsU2NoZWR1bGUgICA9IGN1dGxhc3M6OmdlbW06Oktlcm5lbFB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMVNtTnZmNFNtMTAwOwogICAgICB1c2luZyBFcGlsb2d1ZVNjaGVkdWxlID0gY3V0bGFzczo6ZXBpbG9ndWU6OlB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMVNtOwogICAgfTsKCiAgICBzdHJ1Y3QgTU1BMlNNQ29uZmlnIHsKICAgICAgdXNpbmcgTW1hVGlsZVNoYXBlICAgICA9IGN1dGU6OlNoYXBlPGN1dGU6Ol8yNTYsY3V0ZTo6XzI1NixjdXRlOjpfMjU2PjsKICAgICAgdXNpbmcgS2VybmVsU2NoZWR1bGUgICA9IGN1dGxhc3M6OmdlbW06Oktlcm5lbFB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMlNtTnZmNFNtMTAwOwogICAgICB1c2luZyBFcGlsb2d1ZVNjaGVkdWxlID0gY3V0bGFzczo6ZXBpbG9ndWU6OlB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMlNtOwogICAgfTsKCiAgICB1c2luZyBDb2xsZWN0aXZlRXBpbG9ndWUxU00gPSB0eXBlbmFtZSBjdXRsYXNzOjplcGlsb2d1ZTo6Y29sbGVjdGl2ZTo6Q29sbGVjdGl2ZUJ1aWxkZXI8CiAgICAgICAgQXJjaFRhZywgT3BlcmF0b3JDbGFzcywKICAgICAgICB0eXBlbmFtZSBNTUExU01Db25maWc6Ok1tYVRpbGVTaGFwZSwgQ2x1c3RlclNoYXBlLAogICAgICAgIGN1dGU6OlNoYXBlPGN1dGU6Ol8xMjgsY3V0ZTo6XzY0PiwKICAgICAgICBFbGVtZW50QWNjdW11bGF0b3IsIEVsZW1lbnRBY2N1bXVsYXRvciwKICAgICAgICBFbGVtZW50QywgTGF5b3V0QyAqLCBBbGlnbm1lbnRDLAogICAgICAgIEVsZW1lbnRELCBMYXlvdXRDICosIEFsaWdubWVudEQsCiAgICAgICAgdHlwZW5hbWUgTU1BMVNNQ29uZmlnOjpFcGlsb2d1ZVNjaGVkdWxlCiAgICA+OjpDb2xsZWN0aXZlT3A7CgogICAgdXNpbmcgQ29sbGVjdGl2ZU1haW5sb29wMVNNID0gdHlwZW5hbWUgY3V0bGFzczo6Z2VtbTo6Y29sbGVjdGl2ZTo6Q29sbGVjdGl2ZUJ1aWxkZXI8CiAgICAgIEFyY2hUYWcsIE9wZXJhdG9yQ2xhc3MsCiAgICAgIEVsZW1lbnRBLCBMYXlvdXRBICosIEFsaWdubWVudEEsCiAgICAgIEVsZW1lbnRCLCBMYXlvdXRCICosIEFsaWdubWVudEIsCiAgICAgIEVsZW1lbnRBY2N1bXVsYXRvciwKICAgICAgdHlwZW5hbWUgTU1BMVNNQ29uZmlnOjpNbWFUaWxlU2hhcGUsIENsdXN0ZXJTaGFwZSwKICAgICAgY3V0bGFzczo6Z2VtbTo6Y29sbGVjdGl2ZTo6U3RhZ2VDb3VudEF1dG9DYXJ2ZW91dDwKICAgICAgICBzdGF0aWNfY2FzdDxpbnQ+KHNpemVvZih0eXBlbmFtZSBDb2xsZWN0aXZlRXBpbG9ndWUxU006OlNoYXJlZFN0b3JhZ2UpKT4sCiAgICAgIHR5cGVuYW1lIE1NQTFTTUNvbmZpZzo6S2VybmVsU2NoZWR1bGUKICAgID46OkNvbGxlY3RpdmVPcDsKCiAgICB1c2luZyBHZW1tS2VybmVsMVNNID0gY3V0bGFzczo6Z2VtbTo6a2VybmVsOjpHZW1tVW5pdmVyc2FsPAogICAgICAgIFByb2JsZW1TaGFwZSwKICAgICAgICBDb2xsZWN0aXZlTWFpbmxvb3AxU00sCiAgICAgICAgQ29sbGVjdGl2ZUVwaWxvZ3VlMVNNCiAgICA+OwogICAgdXNpbmcgR2VtbTFTTSA9IGN1dGxhc3M6OmdlbW06OmRldmljZTo6R2VtbVVuaXZlcnNhbEFkYXB0ZXI8R2VtbUtlcm5lbDFTTT47CgogICAgdXNpbmcgQ29sbGVjdGl2ZUVwaWxvZ3VlMlNNID0gdHlwZW5hbWUgY3V0bGFzczo6ZXBpbG9ndWU6OmNvbGxlY3RpdmU6OkNvbGxlY3RpdmVCdWlsZGVyPAogICAgICAgIEFyY2hUYWcsIE9wZXJhdG9yQ2xhc3MsCiAgICAgICAgdHlwZW5hbWUgTU1BMlNNQ29uZmlnOjpNbWFUaWxlU2hhcGUsIENsdXN0ZXJTaGFwZSwKICAgICAgICBjdXRlOjpTaGFwZTxjdXRlOjpfMTI4LGN1dGU6Ol82ND4sCiAgICAgICAgRWxlbWVudEFjY3VtdWxhdG9yLCBFbGVtZW50QWNjdW11bGF0b3IsCiAgICAgICAgRWxlbWVudEMsIExheW91dEMgKiwgQWxpZ25tZW50QywKICAgICAgICBFbGVtZW50RCwgTGF5b3V0QyAqLCBBbGlnbm1lbnRELAogICAgICAgIHR5cGVuYW1lIE1NQTJTTUNvbmZpZzo6RXBpbG9ndWVTY2hlZHVsZQogICAgPjo6Q29sbGVjdGl2ZU9wOwoKICAgIHVzaW5nIENvbGxlY3RpdmVNYWlubG9vcDJTTSA9IHR5cGVuYW1lIGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OkNvbGxlY3RpdmVCdWlsZGVyPAogICAgICBBcmNoVGFnLCBPcGVyYXRvckNsYXNzLAogICAgICBFbGVtZW50QSwgTGF5b3V0QSAqLCBBbGlnbm1lbnRBLAogICAgICBFbGVtZW50QiwgTGF5b3V0QiAqLCBBbGlnbm1lbnRCLAogICAgICBFbGVtZW50QWNjdW11bGF0b3IsCiAgICAgIHR5cGVuYW1lIE1NQTJTTUNvbmZpZzo6TW1hVGlsZVNoYXBlLCBDbHVzdGVyU2hhcGUsCiAgICAgIGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OlN0YWdlQ291bnRBdXRvQ2FydmVvdXQ8CiAgICAgICAgc3RhdGljX2Nhc3Q8aW50PihzaXplb2YodHlwZW5hbWUgQ29sbGVjdGl2ZUVwaWxvZ3VlMlNNOjpTaGFyZWRTdG9yYWdlKSk+LAogICAgICB0eXBlbmFtZSBNTUEyU01Db25maWc6Oktlcm5lbFNjaGVkdWxlCiAgICA+OjpDb2xsZWN0aXZlT3A7CgogICAgdXNpbmcgR2VtbUtlcm5lbDJTTSA9IGN1dGxhc3M6OmdlbW06Omtlcm5lbDo6R2VtbVVuaXZlcnNhbDwKICAgICAgICBQcm9ibGVtU2hhcGUsCiAgICAgICAgQ29sbGVjdGl2ZU1haW5sb29wMlNNLAogICAgICAgIENvbGxlY3RpdmVFcGlsb2d1ZTJTTQogICAgPjsKICAgIHVzaW5nIEdlbW0yU00gPSBjdXRsYXNzOjpnZW1tOjpkZXZpY2U6OkdlbW1Vbml2ZXJzYWxBZGFwdGVyPEdlbW1LZXJuZWwyU00+OwoKICAgIHRlbXBsYXRlIDx0eXBlbmFtZSBUPgogICAgc3RydWN0IFBpbm5lZEhvc3RCdWZmZXIgewogICAgICBUKiBwdHIgPSBudWxscHRyOwogICAgICBzaXplX3QgY291bnQgPSAwOwoKICAgICAgUGlubmVkSG9zdEJ1ZmZlcigpID0gZGVmYXVsdDsKICAgICAgZXhwbGljaXQgUGlubmVkSG9zdEJ1ZmZlcihzaXplX3QgbikgeyBhbGxvY2F0ZShuKTsgfQoKICAgICAgdm9pZCBhbGxvY2F0ZShzaXplX3QgbikgewogICAgICAgIGlmIChwdHIgJiYgY291bnQgPj0gbikgewogICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICByZWxlYXNlKCk7CiAgICAgICAgY291bnQgPSBuOwogICAgICAgIENVREFfQ0hFQ0soY3VkYUhvc3RBbGxvYyhyZWludGVycHJldF9jYXN0PHZvaWQqKj4oJnB0ciksIG4gKiBzaXplb2YoVCksIGN1ZGFIb3N0QWxsb2NQb3J0YWJsZSkpOwogICAgICB9CgogICAgICB2b2lkIHJlbGVhc2UoKSB7CiAgICAgICAgaWYgKHB0cikgewogICAgICAgICAgY3VkYUZyZWVIb3N0KHB0cik7CiAgICAgICAgICBwdHIgPSBudWxscHRyOwogICAgICAgICAgY291bnQgPSAwOwogICAgICAgIH0KICAgICAgfQoKICAgICAgflBpbm5lZEhvc3RCdWZmZXIoKSB7IHJlbGVhc2UoKTsgfQoKICAgICAgVCogZGF0YSgpIGNvbnN0IHsgcmV0dXJuIHB0cjsgfQogICAgICBUJiBvcGVyYXRvcltdKHNpemVfdCBpZHgpIHsgcmV0dXJuIHB0cltpZHhdOyB9CiAgICB9OwoKICAgIHRlbXBsYXRlIDx0eXBlbmFtZSBUPgogICAgc3RydWN0IERldmljZUJ1ZmZlciB7CiAgICAgIFQqIHB0ciA9IG51bGxwdHI7CiAgICAgIHNpemVfdCBjb3VudCA9IDA7CgogICAgICBEZXZpY2VCdWZmZXIoKSA9IGRlZmF1bHQ7CiAgICAgIGV4cGxpY2l0IERldmljZUJ1ZmZlcihzaXplX3QgbikgeyBhbGxvY2F0ZShuKTsgfQoKICAgICAgdm9pZCBhbGxvY2F0ZShzaXplX3QgbikgewogICAgICAgIGlmIChwdHIgJiYgY291bnQgPj0gbikgewogICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICByZWxlYXNlKCk7CiAgICAgICAgY291bnQgPSBuOwogICAgICAgIENVREFfQ0hFQ0soY3VkYU1hbGxvYyhyZWludGVycHJldF9jYXN0PHZvaWQqKj4oJnB0ciksIG4gKiBzaXplb2YoVCkpKTsKICAgICAgfQoKICAgICAgdm9pZCByZWxlYXNlKCkgewogICAgICAgIGlmIChwdHIpIHsKICAgICAgICAgIGN1ZGFGcmVlKHB0cik7CiAgICAgICAgICBwdHIgPSBudWxscHRyOwogICAgICAgICAgY291bnQgPSAwOwogICAgICAgIH0KICAgICAgfQoKICAgICAgfkRldmljZUJ1ZmZlcigpIHsgcmVsZWFzZSgpOyB9CgogICAgICBUKiBnZXQoKSBjb25zdCB7IHJldHVybiBwdHI7IH0KCiAgICAgIHZvaWQgY29weV9mcm9tX2hvc3QoY29uc3QgVCogaG9zdCwgc2l6ZV90IG4sIGN1ZGFTdHJlYW1fdCBzdHJlYW0pIHsKICAgICAgICBDVURBX0NIRUNLKGN1ZGFNZW1jcHlBc3luYyhwdHIsIGhvc3QsIG4gKiBzaXplb2YoVCksIGN1ZGFNZW1jcHlIb3N0VG9EZXZpY2UsIHN0cmVhbSkpOwogICAgICB9CiAgICB9OwoKICAgIHRlbXBsYXRlIDx0eXBlbmFtZSBHZW1tLCBpbnQgQ2x1c3Rlck0sIGludCBDbHVzdGVyTj4KICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IHJ1bl9ncm91cGVkX2ltcGwoCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKSB7CgogICAgICBUT1JDSF9DSEVDSyhBLnNpemUoKSA9PSBCLnNpemUoKSwgIkEvQiBzaXplIG1pc21hdGNoIik7CiAgICAgIFRPUkNIX0NIRUNLKEEuc2l6ZSgpID09IEMuc2l6ZSgpLCAiQS9DIHNpemUgbWlzbWF0Y2giKTsKICAgICAgVE9SQ0hfQ0hFQ0soQS5zaXplKCkgPT0gU0ZBLnNpemUoKSwgIkEvU0ZBIHNpemUgbWlzbWF0Y2giKTsKICAgICAgVE9SQ0hfQ0hFQ0soQS5zaXplKCkgPT0gU0ZCLnNpemUoKSwgIkEvU0ZCIHNpemUgbWlzbWF0Y2giKTsKICAgICAgY29uc3QgaW50MzJfdCBncm91cHMgPSBzdGF0aWNfY2FzdDxpbnQzMl90PihBLnNpemUoKSk7CiAgICAgIFRPUkNIX0NIRUNLKGdyb3VwcyA+IDAsICJObyBncm91cHMiKTsKCiAgICAgIGludCBkZXZpY2VfaWQgPSBBWzBdLmdldF9kZXZpY2UoKTsKICAgICAgYzEwOjpjdWRhOjpDVURBR3VhcmQgZGV2aWNlX2d1YXJkKGRldmljZV9pZCk7CgogICAgICB1c2luZyBTdHJpZGVBID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVBOwogICAgICB1c2luZyBTdHJpZGVCID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVCOwogICAgICB1c2luZyBTdHJpZGVDID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVDOwogICAgICB1c2luZyBTdHJpZGVEID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVEOwogICAgICB1c2luZyBMYXlvdXRTRkEgPSB0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpDb2xsZWN0aXZlTWFpbmxvb3A6OkludGVybmFsTGF5b3V0U0ZBOwogICAgICB1c2luZyBMYXlvdXRTRkIgPSB0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpDb2xsZWN0aXZlTWFpbmxvb3A6OkludGVybmFsTGF5b3V0U0ZCOwogICAgICB1c2luZyBTbTF4eEJsa1NjYWxlZENvbmZpZyA9IHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkNvbGxlY3RpdmVNYWlubG9vcDo6U20xeHhCbGtTY2FsZWRDb25maWc7CgogICAgICBzdGF0aWMgc2l6ZV90IGNhcGFjaXR5ID0gMDsKICAgICAgc3RhdGljIGludCBjYWNoZWRfZGV2aWNlID0gLTE7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRBIGNvbnN0Kj4gcHRyX0FfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEIgY29uc3QqPiBwdHJfQl9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QyBjb25zdCo+IHB0cl9DX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkVwaWxvZ3VlT3V0cHV0T3A6OkVsZW1lbnRPdXRwdXQqPiBwdHJfRF9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPiBwdHJfU0ZBX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkVsZW1lbnRTRiBjb25zdCo+IHB0cl9TRkJfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlQT4gc3RyaWRlX0FfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlQj4gc3RyaWRlX0JfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlQz4gc3RyaWRlX0NfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlRD4gc3RyaWRlX0RfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8TGF5b3V0U0ZBPiBsYXlvdXRfU0ZBX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPExheW91dFNGQj4gbGF5b3V0X1NGQl9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBQcm9ibGVtU2hhcGU6OlVuZGVybHlpbmdQcm9ibGVtU2hhcGU+IHByb2JsZW1fc2l6ZXNfaG9zdDsKCiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgUHJvYmxlbVNoYXBlOjpVbmRlcmx5aW5nUHJvYmxlbVNoYXBlPiBwcm9ibGVtX3NpemVzX2RldmljZTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QSBjb25zdCo+IHB0cl9BOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRCIGNvbnN0Kj4gcHRyX0I7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6RWxlbWVudFNGIGNvbnN0Kj4gcHRyX1NGQTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPiBwdHJfU0ZCOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRDIGNvbnN0Kj4gcHRyX0M7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgR2VtbTo6RXBpbG9ndWVPdXRwdXRPcDo6RWxlbWVudE91dHB1dCo+IHB0cl9EOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUE+IHN0cmlkZV9BOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUI+IHN0cmlkZV9COwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUM+IHN0cmlkZV9DOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUQ+IHN0cmlkZV9EOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPExheW91dFNGQT4gbGF5b3V0X1NGQTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxMYXlvdXRTRkI+IGxheW91dF9TRkI7CiAgICAgIHN0YXRpYyBzaXplX3Qgd29ya3NwYWNlX3NpemVfY2FjaGVkID0gMDsKICAgICAgc3RhdGljIHRvcmNoOjpUZW5zb3Igd29ya3NwYWNlX3RlbnNvcjsKCiAgICAgIGlmIChjYWNoZWRfZGV2aWNlICE9IGRldmljZV9pZCB8fCBjYXBhY2l0eSA8IHN0YXRpY19jYXN0PHNpemVfdD4oZ3JvdXBzKSkgewogICAgICAgIGNhY2hlZF9kZXZpY2UgPSBkZXZpY2VfaWQ7CiAgICAgICAgY2FwYWNpdHkgPSBzdGF0aWNfY2FzdDxzaXplX3Q+KGdyb3Vwcyk7CiAgICAgICAgcHRyX0FfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9CX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQ19ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX0RfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkFfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkJfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9BX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfQl9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0NfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9EX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBsYXlvdXRfU0ZBX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBsYXlvdXRfU0ZCX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwcm9ibGVtX3NpemVzX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKCiAgICAgICAgcHJvYmxlbV9zaXplc19kZXZpY2UuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQS5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9CLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX1NGQS5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkIuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQy5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9ELmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0EuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfQi5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9DLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBsYXlvdXRfU0ZBLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgbGF5b3V0X1NGQi5hbGxvY2F0ZShncm91cHMpOwogICAgICB9CgogICAgICBmb3IgKGludDMyX3QgaSA9IDA7IGkgPCBncm91cHM7ICsraSkgewogICAgICAgIFRPUkNIX0NIRUNLKEFbaV0uaXNfY3VkYSgpLCAiQSBtdXN0IGJlIENVREEiKTsKICAgICAgICBUT1JDSF9DSEVDSyhCW2ldLmlzX2N1ZGEoKSwgIkIgbXVzdCBiZSBDVURBIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soQ1tpXS5pc19jdWRhKCksICJDIG11c3QgYmUgQ1VEQSIpOwogICAgICAgIFRPUkNIX0NIRUNLKFNGQVtpXS5pc19jdWRhKCksICJTRkEgbXVzdCBiZSBDVURBIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soU0ZCW2ldLmlzX2N1ZGEoKSwgIlNGQiBtdXN0IGJlIENVREEiKTsKCiAgICAgICAgaW50MzJfdCBtID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQVtpXS5zaXplKDApKTsKICAgICAgICBpbnQzMl90IGtfcGFja2VkID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQVtpXS5zaXplKDEpKTsKICAgICAgICBpbnQzMl90IGsgPSBrX3BhY2tlZCAqIDI7CiAgICAgICAgaW50MzJfdCBuID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQltpXS5zaXplKDApKTsKICAgICAgICBUT1JDSF9DSEVDSyhCW2ldLnNpemUoMSkgKiAyID09IGssICJLIG1pc21hdGNoIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soQ1tpXS5zaXplKDApID09IG0gJiYgQ1tpXS5zaXplKDEpID09IG4sICJDIHNoYXBlIG1pc21hdGNoIik7CgogICAgICAgIHByb2JsZW1fc2l6ZXNfaG9zdFtpXSA9IGN1dGU6Om1ha2Vfc2hhcGUobSwgbiwgayk7CiAgICAgICAgc3RyaWRlX0FfaG9zdFtpXSA9IGN1dGxhc3M6Om1ha2VfY3V0ZV9wYWNrZWRfc3RyaWRlKFN0cmlkZUF7fSwge20sIGssIDF9KTsKICAgICAgICBzdHJpZGVfQl9ob3N0W2ldID0gY3V0bGFzczo6bWFrZV9jdXRlX3BhY2tlZF9zdHJpZGUoU3RyaWRlQnt9LCB7biwgaywgMX0pOwogICAgICAgIHN0cmlkZV9DX2hvc3RbaV0gPSBjdXRsYXNzOjptYWtlX2N1dGVfcGFja2VkX3N0cmlkZShTdHJpZGVDe30sIHttLCBuLCAxfSk7CiAgICAgICAgc3RyaWRlX0RfaG9zdFtpXSA9IGN1dGxhc3M6Om1ha2VfY3V0ZV9wYWNrZWRfc3RyaWRlKFN0cmlkZUR7fSwge20sIG4sIDF9KTsKICAgICAgICBsYXlvdXRfU0ZBX2hvc3RbaV0gPSBTbTF4eEJsa1NjYWxlZENvbmZpZzo6dGlsZV9hdG9tX3RvX3NoYXBlX1NGQShjdXRlOjptYWtlX3NoYXBlKG0sIG4sIGssIDEpKTsKICAgICAgICBsYXlvdXRfU0ZCX2hvc3RbaV0gPSBTbTF4eEJsa1NjYWxlZENvbmZpZzo6dGlsZV9hdG9tX3RvX3NoYXBlX1NGQihjdXRlOjptYWtlX3NoYXBlKG0sIG4sIGssIDEpKTsKCiAgICAgICAgcHRyX0FfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEEgY29uc3QqPihBW2ldLmRhdGFfcHRyKCkpOwogICAgICAgIHB0cl9CX2hvc3RbaV0gPSByZWludGVycHJldF9jYXN0PHR5cGVuYW1lIEdlbW06OkVsZW1lbnRCIGNvbnN0Kj4oQltpXS5kYXRhX3B0cigpKTsKICAgICAgICBwdHJfQ19ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QyBjb25zdCo+KENbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX0RfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6RXBpbG9ndWVPdXRwdXRPcDo6RWxlbWVudE91dHB1dCo+KENbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX1NGQV9ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPihTRkFbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX1NGQl9ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPihTRkJbaV0uZGF0YV9wdHIoKSk7CiAgICAgIH0KCiAgICAgIGF1dG8gc3RyZWFtX29iaiA9IGMxMDo6Y3VkYTo6Z2V0Q3VycmVudENVREFTdHJlYW0oZGV2aWNlX2lkKTsKICAgICAgYzEwOjpjdWRhOjpDVURBU3RyZWFtR3VhcmQgc3RyZWFtX2d1YXJkKHN0cmVhbV9vYmopOwogICAgICBjdWRhU3RyZWFtX3Qgc3RyZWFtID0gc3RyZWFtX29iai5zdHJlYW0oKTsKICAgICAgcHJvYmxlbV9zaXplc19kZXZpY2UuY29weV9mcm9tX2hvc3QocHJvYmxlbV9zaXplc19ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfQS5jb3B5X2Zyb21faG9zdChwdHJfQV9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfQi5jb3B5X2Zyb21faG9zdChwdHJfQl9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfU0ZBLmNvcHlfZnJvbV9ob3N0KHB0cl9TRkFfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKICAgICAgcHRyX1NGQi5jb3B5X2Zyb21faG9zdChwdHJfU0ZCX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHB0cl9DLmNvcHlfZnJvbV9ob3N0KHB0cl9DX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHB0cl9ELmNvcHlfZnJvbV9ob3N0KHB0cl9EX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9BLmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9BX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9CLmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9CX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9DLmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9DX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9ELmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9EX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIGxheW91dF9TRkEuY29weV9mcm9tX2hvc3QobGF5b3V0X1NGQV9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBsYXlvdXRfU0ZCLmNvcHlfZnJvbV9ob3N0KGxheW91dF9TRkJfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKCiAgICAgIGN1dGxhc3M6Oktlcm5lbEhhcmR3YXJlSW5mbyBod19pbmZvOwogICAgICBod19pbmZvLmRldmljZV9pZCA9IGRldmljZV9pZDsKICAgICAgaHdfaW5mby5zbV9jb3VudCA9IGN1dGxhc3M6Oktlcm5lbEhhcmR3YXJlSW5mbzo6cXVlcnlfZGV2aWNlX211bHRpcHJvY2Vzc29yX2NvdW50KGh3X2luZm8uZGV2aWNlX2lkKTsKICAgICAgaHdfaW5mby5jbHVzdGVyX3NoYXBlID0gZGltMyhDbHVzdGVyTSwgQ2x1c3Rlck4sIDEpOwogICAgICBod19pbmZvLmNsdXN0ZXJfc2hhcGVfZmFsbGJhY2sgPSBkaW0zKENsdXN0ZXJNLCBDbHVzdGVyTiwgMSk7CgogICAgICB0eXBlbmFtZSBHZW1tOjpBcmd1bWVudHMgYXJndW1lbnRzOwogICAgICBkZWNsdHlwZShhcmd1bWVudHMuZXBpbG9ndWUudGhyZWFkKSBmdXNpb25fYXJnczsKICAgICAgZnVzaW9uX2FyZ3MuYWxwaGFfcHRyID0gbnVsbHB0cjsKICAgICAgZnVzaW9uX2FyZ3MuYmV0YV9wdHIgPSBudWxscHRyOwogICAgICBmdXNpb25fYXJncy5hbHBoYSA9IEVsZW1lbnRBY2N1bXVsYXRvcigxKTsKICAgICAgZnVzaW9uX2FyZ3MuYmV0YSA9IEVsZW1lbnRBY2N1bXVsYXRvcigwKTsKICAgICAgZnVzaW9uX2FyZ3MuYWxwaGFfcHRyX2FycmF5ID0gbnVsbHB0cjsKICAgICAgZnVzaW9uX2FyZ3MuYmV0YV9wdHJfYXJyYXkgPSBudWxscHRyOwogICAgICBmdXNpb25fYXJncy5kQWxwaGEgPSB7Y3V0ZTo6XzB7fSwgY3V0ZTo6XzB7fSwgMH07CiAgICAgIGZ1c2lvbl9hcmdzLmRCZXRhID0ge2N1dGU6Ol8we30sIGN1dGU6Ol8we30sIDB9OwoKICAgICAgdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6VGlsZVNjaGVkdWxlckFyZ3VtZW50cyBzY2hlZHVsZXI7CiAgICAgIHNjaGVkdWxlci5yYXN0ZXJfb3JkZXIgPSBjdXRsYXNzOjpnZW1tOjprZXJuZWw6OmRldGFpbDo6UmFzdGVyT3JkZXJPcHRpb25zOjpBbG9uZ047CgogICAgICBhcmd1bWVudHMgPSB0eXBlbmFtZSBHZW1tOjpBcmd1bWVudHMgewogICAgICAgIGN1dGxhc3M6OmdlbW06OkdlbW1Vbml2ZXJzYWxNb2RlOjprR3JvdXBlZCwKICAgICAgICB7Z3JvdXBzLCBwcm9ibGVtX3NpemVzX2RldmljZS5nZXQoKSwgbnVsbHB0cn0sCiAgICAgICAge3B0cl9BLmdldCgpLCBzdHJpZGVfQS5nZXQoKSwgcHRyX0IuZ2V0KCksIHN0cmlkZV9CLmdldCgpLAogICAgICAgICBwdHJfU0ZBLmdldCgpLCBsYXlvdXRfU0ZBLmdldCgpLCBwdHJfU0ZCLmdldCgpLCBsYXlvdXRfU0ZCLmdldCgpfSwKICAgICAgICB7ZnVzaW9uX2FyZ3MsIHB0cl9DLmdldCgpLCBzdHJpZGVfQy5nZXQoKSwgcHRyX0QuZ2V0KCksIHN0cmlkZV9ELmdldCgpfSwKICAgICAgICBod19pbmZvLCBzY2hlZHVsZXIKICAgICAgfTsKCiAgICAgIEdlbW0gZ2VtbTsKICAgICAgc2l6ZV90IHdvcmtzcGFjZV9zaXplID0gR2VtbTo6Z2V0X3dvcmtzcGFjZV9zaXplKGFyZ3VtZW50cyk7CiAgICAgIGlmICghd29ya3NwYWNlX3RlbnNvci5kZWZpbmVkKCkgfHwgY2FjaGVkX2RldmljZSAhPSBkZXZpY2VfaWQgfHwgd29ya3NwYWNlX3NpemVfY2FjaGVkIDwgd29ya3NwYWNlX3NpemUpIHsKICAgICAgICBhdXRvIG9wdHMgPSB0b3JjaDo6VGVuc29yT3B0aW9ucygpLmRldmljZSh0b3JjaDo6a0NVREEsIGRldmljZV9pZCkuZHR5cGUodG9yY2g6OmtVSW50OCk7CiAgICAgICAgd29ya3NwYWNlX3RlbnNvciA9IHRvcmNoOjplbXB0eSh7c3RhdGljX2Nhc3Q8bG9uZz4od29ya3NwYWNlX3NpemUpfSwgb3B0cyk7CiAgICAgICAgd29ya3NwYWNlX3NpemVfY2FjaGVkID0gd29ya3NwYWNlX3NpemU7CiAgICAgIH0KICAgICAgdm9pZCogd29ya3NwYWNlX3B0ciA9IHdvcmtzcGFjZV9zaXplID8gd29ya3NwYWNlX3RlbnNvci5kYXRhX3B0cigpIDogbnVsbHB0cjsKCiAgICAgIENVVExBU1NfQ0hFQ0soZ2VtbS5jYW5faW1wbGVtZW50KGFyZ3VtZW50cykpOwogICAgICBDVVRMQVNTX0NIRUNLKGdlbW0uaW5pdGlhbGl6ZShhcmd1bWVudHMsIHdvcmtzcGFjZV9wdHIsIHN0cmVhbSkpOwogICAgICBDVVRMQVNTX0NIRUNLKGdlbW0ucnVuKHN0cmVhbSkpOwoKICAgICAgcmV0dXJuIEM7CiAgICB9CgogICAgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4gZ3JvdXBlZF9nZW1tXzFzbSgKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgQiwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgQywKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZBLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBTRkIpIHsKICAgICAgcmV0dXJuIHJ1bl9ncm91cGVkX2ltcGw8R2VtbTFTTSwgMiwgMT4oQSwgQiwgQywgU0ZBLCBTRkIpOwogICAgfQoKICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IGdyb3VwZWRfZ2VtbV8yc20oCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKSB7CiAgICAgIHJldHVybiBydW5fZ3JvdXBlZF9pbXBsPEdlbW0yU00sIDIsIDE+KEEsIEIsIEMsIFNGQSwgU0ZCKTsKICAgIH0KICAgIAo="
_ext_mod = None
_ext_fail = False
_use_ext = False
_force_ext_only = False
_use_v2 = True
def _dec(b64: str) -> str:
return base64.b64decode(b64).decode("utf-8")
def _find_inc() -> list[str]:
incs: list[str] = []
def _add_cutlass(base: str) -> bool:
inc = os.path.join(base, "include")
if os.path.isfile(os.path.join(inc, "cutlass", "cutlass.h")):
incs.append(inc)
util_inc = os.path.join(base, "tools", "util", "include")
if os.path.isfile(os.path.join(util_inc, "cutlass", "util", "device_memory.h")):
incs.append(util_inc)
return True
inc2 = os.path.join(base, "cutlass", "include")
if os.path.isfile(os.path.join(inc2, "cutlass", "cutlass.h")):
incs.append(inc2)
util_inc2 = os.path.join(base, "cutlass", "tools", "util", "include")
if os.path.isfile(os.path.join(util_inc2, "cutlass", "util", "device_memory.h")):
incs.append(util_inc2)
return True
return False
try:
import cutlass as _c
root = os.path.dirname(getattr(_c, "__file__", "") or "")
if root:
bases = [root, os.path.dirname(root), os.path.dirname(os.path.dirname(root))]
for base in bases:
if _add_cutlass(base):
break
except Exception:
pass
try:
import torch.utils.cpp_extension as _ce
for inc in _ce.include_paths():
if os.path.isfile(os.path.join(inc, "cutlass", "cutlass.h")):
incs.append(inc)
util_inc3 = os.path.join(inc, "cutlass", "tools", "util", "include")
if os.path.isfile(os.path.join(util_inc3, "cutlass", "util", "device_memory.h")):
incs.append(util_inc3)
except Exception:
pass
if not incs:
root = _ensure_cutlass_dir()
if root:
_add_cutlass(root)
return incs
_cutlass_root_cached: str | None = None
def _ensure_cutlass_dir() -> str | None:
global _cutlass_root_cached
if _cutlass_root_cached is not None:
return _cutlass_root_cached
base_dir = "/tmp/cutlass_cache"
root = os.path.join(base_dir, "cutlass-4.3.5")
inc = os.path.join(root, "include", "cutlass", "cutlass.h")
if os.path.isfile(inc):
_cutlass_root_cached = root
return root
try:
os.makedirs(base_dir, exist_ok=True)
tgz_path = os.path.join(base_dir, "cutlass.tgz")
if not os.path.isfile(tgz_path):
import urllib.request
url = "https://github.com/NVIDIA/cutlass/archive/refs/tags/v4.3.5.tar.gz"
with urllib.request.urlopen(url, timeout=60) as resp:
data = resp.read()
with open(tgz_path, "wb") as f:
f.write(data)
import tarfile
with tarfile.open(tgz_path, "r:gz") as tf:
tf.extractall(base_dir)
if os.path.isfile(inc):
_cutlass_root_cached = root
return root
except Exception:
return None
return None
def _load_ext():
global _ext_mod, _ext_fail
if _ext_mod is not None or _ext_fail:
return _ext_mod
incs = _find_inc()
try:
from torch.utils.cpp_extension import load_inline
os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "10.0a")
cpp_src = _dec(_CPP_B64)
cu_src = _dec(_CU_B64)
_ext_mod = load_inline(
name="nvfp4_grouped_cutlass_ext",
cpp_sources=cpp_src,
cuda_sources=cu_src,
functions=["grouped_gemm_1sm", "grouped_gemm_2sm"],
extra_cuda_cflags=[
"-O3",
"--use_fast_math",
"-std=c++17",
"--diag-suppress=144",
],
extra_ldflags=["-lcublasLt", "-lcublas", "-lcuda"],
extra_cflags=["-O3", "-std=c++17"],
with_cuda=True,
extra_include_paths=incs,
verbose=False,
)
except Exception as exc:
_ext_fail = True
_ext_mod = None
if _force_ext_only:
raise RuntimeError(f"ext build failed: {exc}") from exc
return _ext_mod
def _try_ext(
abc_tensors: list[tuple[torch.Tensor, torch.Tensor, torch.Tensor]],
sfasfb_reordered_tensors: list[tuple[torch.Tensor, torch.Tensor]],
problem_sizes: list[tuple[int, int, int, int]],
):
if not _use_ext:
return None
ext = _load_ext()
if ext is None:
return None
a_list = []
b_list = []
c_list = []
sfa_list = []
sfb_list = []
max_m = 0
for (a_ref, b_ref, c_ref), (sfa_reordered, sfb_reordered), (_, _, _, l) in zip(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
):
if l != 1:
return None
a2 = a_ref[:, :, 0]
b2 = b_ref[:, :, 0]
c2 = c_ref[:, :, 0]
a_list.append(a2)
b_list.append(b2)
c_list.append(c2)
sfa_list.append(sfa_reordered)
sfb_list.append(sfb_reordered)
max_m = max(max_m, a2.shape[0])
try:
if max_m >= 512:
ext.grouped_gemm_2sm(a_list, b_list, c_list, sfa_list, sfb_list)
else:
ext.grouped_gemm_1sm(a_list, b_list, c_list, sfa_list, sfb_list)
except Exception:
if _force_ext_only:
raise
return None
return [c_ref for (_, _, c_ref) in abc_tensors]
_scale_cache: dict[tuple[int, int], torch.Tensor] = {}
_scaled_mm_out_supported: bool | None = None
_scaled_mm_fast_accum_supported: bool | None = None
_grouped_mm_v2_supported: bool | None = None
_grouped_mm_v2_fn = None
_grouped_mm_supported: bool | None = False
_group_cache: dict[tuple, dict] = {}
_group_cache_padded: dict[tuple, dict] = {}
_grouped_strategy_cache: dict[tuple, str] = {}
_grouped_strategy_cache_v1: dict[tuple, str] = {}
_batch_cache: dict[tuple, dict] = {}
_batched_supported: bool | None = False
_cute_supported: bool | None = False
_scale_recipe_cached = None
_global_scale_recipe_cached = None
_swizzle_cached = None
_global_swizzle_cached = None
_global_scale_cache: dict[tuple[int, int, int], torch.Tensor] = {}
_dbg_on = bool(os.environ.get("NVFP4_DEBUG"))
_dbg_once = False
def _dbg(msg: str) -> None:
if _dbg_on:
print(msg)
def blocked_from_reordered(sf_reordered: torch.Tensor, l_idx: int) -> torch.Tensor:
key = (sf_reordered.data_ptr(), l_idx)
cached = _scale_cache.get(key)
if cached is not None:
return cached
perm = sf_reordered.permute(2, 4, 0, 1, 3, 5)
flat = perm.reshape(-1, 32, 16, sf_reordered.shape[-1])[..., l_idx].reshape(-1)
_scale_cache[key] = flat
return flat
def _round_up(x: int, m: int) -> int:
return (x + m - 1) // m * m
def _get_grouped_mm_v2():
global _grouped_mm_v2_supported, _grouped_mm_v2_fn
if _grouped_mm_v2_supported is None:
try:
_grouped_mm_v2_fn = torch.nn.functional.scaled_grouped_mm
_grouped_mm_v2_supported = True
except Exception:
try:
_grouped_mm_v2_fn = torch.ops.aten._scaled_grouped_mm_v2
_grouped_mm_v2_supported = True
except Exception:
_grouped_mm_v2_supported = False
_grouped_mm_v2_fn = None
return None
if _grouped_mm_v2_supported:
return _grouped_mm_v2_fn
return None
def _get_grouped_mm():
global _grouped_mm_supported
if _grouped_mm_supported is None:
try:
_grouped_mm_supported = True
return torch.ops.aten._scaled_grouped_mm
except Exception:
_grouped_mm_supported = False
return None
if _grouped_mm_supported:
return torch.ops.aten._scaled_grouped_mm
return None
def _find_enum_value(enum_obj, target: str):
key = target.replace("_", "").upper()
for attr in dir(enum_obj):
if attr.startswith("_"):
continue
attr_key = attr.replace("_", "").upper()
if attr_key == key:
return getattr(enum_obj, attr)
return None
def _enum_to_int(val):
try:
return int(val)
except Exception:
return val
def _get_scale_swizzle():
global _scale_recipe_cached, _swizzle_cached
global _global_scale_recipe_cached, _global_swizzle_cached
if _scale_recipe_cached is not None and _swizzle_cached is not None:
return (
_scale_recipe_cached,
_global_scale_recipe_cached,
_swizzle_cached,
_global_swizzle_cached,
)
scaling_type = None
swizzle_type = None
for mod in (
getattr(torch, "nn", None).functional if hasattr(torch, "nn") else None,
torch,
getattr(torch, "_C", None),
):
if mod is None:
continue
if scaling_type is None:
scaling_type = getattr(mod, "ScalingType", None) or getattr(mod, "_ScalingType", None)
if swizzle_type is None:
swizzle_type = getattr(mod, "SwizzleType", None) or getattr(mod, "_SwizzleType", None)
if scaling_type is not None and swizzle_type is not None:
break
if scaling_type is None or swizzle_type is None:
return None, None, None, None
block_scale = _find_enum_value(scaling_type, "BLOCKWISE_1X16")
if block_scale is None:
block_scale = _find_enum_value(scaling_type, "BLOCKWISE1X16")
if block_scale is None:
block_scale = _find_enum_value(scaling_type, "BLOCKWISE_16")
if block_scale is None:
block_scale = _find_enum_value(scaling_type, "BLOCKWISE16")
global_scale = _find_enum_value(scaling_type, "TENSORWISE")
if global_scale is None:
global_scale = _find_enum_value(scaling_type, "TENSOR_WISE")
if global_scale is None:
global_scale = _find_enum_value(scaling_type, "TENSOR")
block_swizzle = _find_enum_value(swizzle_type, "SWIZZLE_32_4_4")
global_swizzle = _find_enum_value(swizzle_type, "NONE")
_scale_recipe_cached = block_scale
_global_scale_recipe_cached = global_scale
_swizzle_cached = block_swizzle
_global_swizzle_cached = global_swizzle
return block_scale, global_scale, block_swizzle, global_swizzle
def _get_global_scale(num_groups: int, device: torch.device, per_group: bool) -> torch.Tensor:
key = (
device.index if device.type == "cuda" else -1,
num_groups if per_group else 1,
1 if per_group else 0,
)
cached = _global_scale_cache.get(key)
if cached is not None and cached.device == device:
return cached
shape = (num_groups, 1) if per_group else (1,)
scale = torch.ones(shape, device=device, dtype=torch.float32)
_global_scale_cache[key] = scale
return scale
def _build_grouped_inputs(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
) -> dict:
key = []
for (a_ref, b_ref, _), (sfa_reordered, sfb_reordered) in zip(
abc_tensors, sfasfb_reordered_tensors
):
key.append(
(
a_ref.data_ptr(),
b_ref.data_ptr(),
sfa_reordered.data_ptr(),
sfb_reordered.data_ptr(),
a_ref.shape,
b_ref.shape,
)
)
key = tuple(key)
cached = _group_cache.get(key)
if cached is not None:
return cached
k_val = problem_sizes[0][2] if problem_sizes else 0
k_blocks = _round_up((k_val + 15) // 16, 4)
a_blocks = []
b_blocks = []
scale_a_blocks = []
scale_b_blocks = []
offs = []
m_pads = []
m_actual = []
offset = 0
for (a_ref, b_ref, _), (sfa_reordered, sfb_reordered), (m, _, _, l) in zip(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
):
if l != 1:
raise RuntimeError("grouped path only supports l=1")
m_pad = _round_up(m, 128)
m_pads.append(m_pad)
m_actual.append(m)
if m_pad == m:
a_pad = a_ref[:, :, 0]
else:
a_pad = torch.zeros(
(m_pad, a_ref.shape[1]), device=a_ref.device, dtype=a_ref.dtype
)
a_pad[:m].copy_(a_ref[:, :, 0])
a_blocks.append(a_pad)
b_blocks.append(b_ref[:, :, 0])
scale_a = blocked_from_reordered(sfa_reordered, 0)
scale_expected = m_pad * k_blocks
if scale_a.numel() < scale_expected:
pad_len = scale_expected - scale_a.numel()
scale_a = torch.cat(
[
scale_a,
torch.zeros((pad_len,), device=scale_a.device, dtype=scale_a.dtype),
],
dim=0,
)
scale_a_blocks.append(scale_a.view(m_pad, k_blocks))
scale_b_blocks.append(blocked_from_reordered(sfb_reordered, 0))
offset += m_pad
offs.append(offset)
m_pad_max = max(m_pads) if m_pads else 0
scale_len_pad = m_pad_max * k_blocks
a_concat = torch.cat(a_blocks, dim=0)
b_nk = torch.stack(b_blocks, dim=0)
scale_a = torch.cat(scale_a_blocks, dim=0)
scale_b = torch.stack(scale_b_blocks, dim=0)
offs_cpu = torch.tensor(offs, dtype=torch.int32)
offs_tensor = offs_cpu.to(device=a_concat.device)
packed = {
"a": a_concat,
"b_nk": b_nk,
"b_kn": None,
"scale_a": scale_a,
"scale_a_stack": None,
"scale_a_blocks": scale_a_blocks,
"scale_len_pad": scale_len_pad,
"scale_b": scale_b,
"offs": offs_tensor,
"offs_cpu": offs_cpu,
"m_pads": m_pads,
"m_actual": m_actual,
}
_group_cache[key] = packed
return packed
def _build_grouped_inputs_padded(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
) -> dict:
key = []
for (a_ref, b_ref, _), (sfa_reordered, sfb_reordered) in zip(
abc_tensors, sfasfb_reordered_tensors
):
key.append(
(
a_ref.data_ptr(),
b_ref.data_ptr(),
sfa_reordered.data_ptr(),
sfb_reordered.data_ptr(),
a_ref.shape,
b_ref.shape,
)
)
key = tuple(key)
cached = _group_cache_padded.get(key)
if cached is not None:
return cached
ms = [m for (m, _, _, _) in problem_sizes]
m_pad = _round_up(max(ms), 128) if ms else 0
k_val = problem_sizes[0][2] if problem_sizes else 0
k_blocks = _round_up((k_val + 15) // 16, 4)
scale_len_pad = m_pad * k_blocks
a_blocks = []
b_blocks = []
scale_a_blocks = []
scale_b_blocks = []
m_actual = []
for (a_ref, b_ref, _), (sfa_reordered, sfb_reordered), (m, _, _, l) in zip(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
):
if l != 1:
raise RuntimeError("grouped path only supports l=1")
m_actual.append(m)
a_pad = torch.zeros(
(m_pad, a_ref.shape[1]), device=a_ref.device, dtype=a_ref.dtype
)
a_pad[:m].copy_(a_ref[:, :, 0])
a_blocks.append(a_pad)
b_blocks.append(b_ref[:, :, 0])
scale_a = blocked_from_reordered(sfa_reordered, 0)
if scale_a.numel() < scale_len_pad:
pad_len = scale_len_pad - scale_a.numel()
scale_a = torch.cat(
[
scale_a,
torch.zeros((pad_len,), device=scale_a.device, dtype=scale_a.dtype),
],
dim=0,
)
scale_a_blocks.append(scale_a)
scale_b_blocks.append(blocked_from_reordered(sfb_reordered, 0))
a_stack = torch.stack(a_blocks, dim=0) if a_blocks else None
b_nk = torch.stack(b_blocks, dim=0) if b_blocks else None
b_kn = None
scale_a = torch.stack(scale_a_blocks, dim=0) if scale_a_blocks else None
scale_b = torch.stack(scale_b_blocks, dim=0) if scale_b_blocks else None
packed = {
"a": a_stack,
"b_nk": b_nk,
"b_kn": b_kn,
"scale_a": scale_a,
"scale_b": scale_b,
"m_actual": m_actual,
"m_pad": m_pad,
}
_group_cache_padded[key] = packed
return packed
def _build_batched_inputs(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
) -> dict:
key = []
for (a_ref, b_ref, _), (sfa_reordered, sfb_reordered) in zip(
abc_tensors, sfasfb_reordered_tensors
):
key.append(
(
a_ref.data_ptr(),
b_ref.data_ptr(),
sfa_reordered.data_ptr(),
sfb_reordered.data_ptr(),
a_ref.shape,
b_ref.shape,
)
)
key = tuple(key)
cached = _batch_cache.get(key)
if cached is not None:
return cached
ms = [m for (m, _, _, _) in problem_sizes]
ns = [n for (_, n, _, _) in problem_sizes]
ks = [k for (_, _, k, _) in problem_sizes]
g = len(problem_sizes)
if not ms or not ns or not ks:
raise RuntimeError("empty problems")
m_pad = _round_up(max(ms), 128)
n_pad = _round_up(max(ns), 128)
k_pad = _round_up(max(ks), 256)
k_packed_pad = k_pad // 2
rest_m_pad = ceil_div(m_pad, 128)
rest_n_pad = ceil_div(n_pad, 128)
sf_k_pad = ceil_div(k_pad, 16)
rest_k_pad = ceil_div(sf_k_pad, 4)
device = abc_tensors[0][0].device
a_dtype = abc_tensors[0][0].dtype
b_dtype = abc_tensors[0][1].dtype
sfa_dtype = sfasfb_reordered_tensors[0][0].dtype
sfb_dtype = sfasfb_reordered_tensors[0][1].dtype
a_batch = torch.zeros((g, m_pad, k_packed_pad), device=device, dtype=a_dtype)
b_batch = torch.zeros((g, n_pad, k_packed_pad), device=device, dtype=b_dtype)
c_batch = torch.empty((g, m_pad, n_pad), device=device, dtype=torch.float16)
sfa_batch = torch.zeros(
(32, 4, rest_m_pad, 4, rest_k_pad, g),
device=device,
dtype=sfa_dtype,
)
sfb_batch = torch.zeros(
(32, 4, rest_n_pad, 4, rest_k_pad, g),
device=device,
dtype=sfb_dtype,
)
m_actual = []
n_actual = []
k_actual = []
for idx, ((a_ref, b_ref, _), (sfa_reordered, sfb_reordered), (m, n, k, l)) in enumerate(
zip(abc_tensors, sfasfb_reordered_tensors, problem_sizes)
):
if l != 1:
raise RuntimeError("batched path only supports l=1")
m_actual.append(m)
n_actual.append(n)
k_actual.append(k)
a_batch[idx, :m, : (k // 2)].copy_(a_ref[:, :, 0])
b_batch[idx, :n, : (k // 2)].copy_(b_ref[:, :, 0])
sfa_src = sfa_reordered[..., 0]
sfb_src = sfb_reordered[..., 0]
sfa_batch[
:, :, : sfa_src.shape[2], :, : sfa_src.shape[4], idx
].copy_(sfa_src)
sfb_batch[
:, :, : sfb_src.shape[2], :, : sfb_src.shape[4], idx
].copy_(sfb_src)
packed = {
"a": a_batch,
"b": b_batch,
"c": c_batch,
"sfa": sfa_batch,
"sfb": sfb_batch,
"m_pad": m_pad,
"n_pad": n_pad,
"k_pad": k_pad,
"m_actual": m_actual,
"n_actual": n_actual,
"k_actual": k_actual,
}
_batch_cache[key] = packed
return packed
def _batch_partitions(problem_sizes: list[tuple[int, int, int, int]], max_ratio: float) -> list[list[int]]:
if not problem_sizes:
return []
for _, _, _, l in problem_sizes:
if l != 1:
return []
idxs = list(range(len(problem_sizes)))
idxs.sort(key=lambda i: problem_sizes[i][0])
batches: list[list[int]] = []
cur: list[int] = []
m_min = 0
for idx in idxs:
m = problem_sizes[idx][0]
if not cur:
cur = [idx]
m_min = m
continue
if m_min == 0 or (m / m_min) <= max_ratio:
cur.append(idx)
else:
batches.append(cur)
cur = [idx]
m_min = m
if cur:
batches.append(cur)
return batches
def _use_cute_for_sizes(problem_sizes: list[tuple[int, int, int, int]]) -> bool:
if not problem_sizes:
return False
n_min = None
k_min = None
for _, n, k, l in problem_sizes:
if l != 1:
return False
n_min = n if n_min is None else min(n_min, n)
k_min = k if k_min is None else min(k_min, k)
if n_min is None or k_min is None:
return False
return n_min >= 2048 and k_min >= 1024
def _try_scaled_grouped_mm_v2(
func,
a,
b,
scale_a,
scale_recipe_a,
scale_b,
scale_recipe_b,
swizzle_a,
swizzle_b,
offs,
offs_cpu,
output_dtype,
):
for offs_arg in (offs_cpu, offs):
try:
return func(
a,
b,
scale_a,
scale_recipe_a,
scale_b,
scale_recipe_b,
swizzle_a=swizzle_a,
swizzle_b=swizzle_b,
bias=None,
offs=offs_arg,
output_dtype=output_dtype,
use_fast_accum=True,
)
except TypeError:
try:
return func(
a,
b,
scale_a,
scale_recipe_a,
scale_b,
scale_recipe_b,
swizzle_a=swizzle_a,
swizzle_b=swizzle_b,
bias=None,
offs=offs_arg,
output_dtype=output_dtype,
contraction_dim=(),
use_fast_accum=True,
)
except Exception:
continue
except Exception:
continue
return None
def _try_scaled_grouped_mm_v1(
func,
a,
b,
scale_a,
scale_b,
offs,
offs_cpu,
output_dtype,
):
for offs_arg in (offs_cpu, offs):
try:
return func(
a,
b,
scale_a,
scale_b,
offs_arg,
None,
None,
output_dtype,
False,
)
except Exception:
continue
return None
def _scaled_mm_out(mat_a, mat_b, scale_a, scale_b, out_tensor):
global _scaled_mm_out_supported, _scaled_mm_fast_accum_supported
if _scaled_mm_out_supported is None:
try:
torch._scaled_mm(
mat_a,
mat_b,
scale_a,
scale_b,
bias=None,
out_dtype=torch.float16,
use_fast_accum=True,
out=out_tensor,
)
_scaled_mm_out_supported = True
_scaled_mm_fast_accum_supported = True
return out_tensor
except Exception:
try:
torch._scaled_mm(
mat_a,
mat_b,
scale_a,
scale_b,
bias=None,
out_dtype=torch.float16,
out=out_tensor,
)
_scaled_mm_out_supported = True
_scaled_mm_fast_accum_supported = False
return out_tensor
except Exception:
_scaled_mm_out_supported = False
if _scaled_mm_out_supported:
if _scaled_mm_fast_accum_supported:
torch._scaled_mm(
mat_a,
mat_b,
scale_a,
scale_b,
bias=None,
out_dtype=torch.float16,
use_fast_accum=True,
out=out_tensor,
)
else:
torch._scaled_mm(
mat_a,
mat_b,
scale_a,
scale_b,
bias=None,
out_dtype=torch.float16,
out=out_tensor,
)
return out_tensor
if _scaled_mm_fast_accum_supported:
out = torch._scaled_mm(
mat_a,
mat_b,
scale_a,
scale_b,
bias=None,
out_dtype=torch.float16,
use_fast_accum=True,
)
else:
out = torch._scaled_mm(
mat_a,
mat_b,
scale_a,
scale_b,
bias=None,
out_dtype=torch.float16,
)
out_tensor.copy_(out)
return out_tensor
def _get_b_kn(grouped: dict) -> torch.Tensor | None:
b_kn = grouped.get("b_kn")
if b_kn is None:
b_nk = grouped.get("b_nk")
if b_nk is None:
return None
if b_nk.dim() == 2:
b_kn = b_nk.transpose(0, 1)
else:
b_kn = b_nk.transpose(1, 2)
grouped["b_kn"] = b_kn
return b_kn
def _get_scale_a_stack(grouped: dict) -> torch.Tensor | None:
scale_a_stack = grouped.get("scale_a_stack")
if scale_a_stack is not None:
return scale_a_stack
scale_a_blocks = grouped.get("scale_a_blocks")
scale_len_pad = grouped.get("scale_len_pad")
if not scale_a_blocks or scale_len_pad is None:
return None
stack = []
for scale_a in scale_a_blocks:
scale_a = scale_a.reshape(-1)
if scale_a.numel() < scale_len_pad:
pad_len = scale_len_pad - scale_a.numel()
scale_a = torch.cat(
[
scale_a,
torch.zeros((pad_len,), device=scale_a.device, dtype=scale_a.dtype),
],
dim=0,
)
stack.append(scale_a)
if stack:
scale_a_stack = torch.stack(stack, dim=0)
else:
scale_a_stack = None
grouped["scale_a_stack"] = scale_a_stack
return scale_a_stack
def custom_kernel(data: input_t) -> output_t:
global _batched_supported, _cute_supported
abc_tensors, _, sfasfb_reordered_tensors, problem_sizes = data
if _dbg_on:
try:
doc = getattr(torch.nn.functional.scaled_grouped_mm, "__doc__", None)
if doc:
_dbg("v2doc:" + doc[:600].replace("\n", " "))
else:
_dbg("v2doc:none")
except Exception as exc:
_dbg("v2docerr:" + str(exc))
try:
_dbg("v1op:" + str(torch.ops.aten._scaled_grouped_mm))
except Exception as exc:
_dbg("v1operr:" + str(exc))
ext_res = _try_ext(abc_tensors, sfasfb_reordered_tensors, problem_sizes)
if ext_res is not None:
_dbg("path:ext")
return ext_res
if _force_ext_only:
raise RuntimeError("ext path failed")
if _batched_supported is not False:
try:
batch_sets = _batch_partitions(problem_sizes, 4.0)
if batch_sets:
compiled_func = compile_kernel()
batched_results: list[torch.Tensor | None] = [
None for _ in range(len(abc_tensors))
]
for batch in batch_sets:
sub_abc = [abc_tensors[i] for i in batch]
sub_sfa = [sfasfb_reordered_tensors[i] for i in batch]
sub_sizes = [problem_sizes[i] for i in batch]
batched = _build_batched_inputs(sub_abc, sub_sfa, sub_sizes)
a_ptr = make_ptr(
ab_dtype,
batched["a"].data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
b_ptr = make_ptr(
ab_dtype,
batched["b"].data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
c_ptr = make_ptr(
c_dtype,
batched["c"].data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
sfa_ptr = make_ptr(
sf_dtype,
batched["sfa"].data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
sfb_ptr = make_ptr(
sf_dtype,
batched["sfb"].data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
compiled_func(
a_ptr,
b_ptr,
sfa_ptr,
sfb_ptr,
c_ptr,
(
batched["m_pad"],
batched["n_pad"],
batched["k_pad"],
len(batch),
),
)
for local_idx, orig_idx in enumerate(batch):
m = sub_sizes[local_idx][0]
n = sub_sizes[local_idx][1]
c_ref = abc_tensors[orig_idx][2]
c_ref[:, :, 0].copy_(batched["c"][local_idx, :m, :n])
batched_results[orig_idx] = c_ref
if all(item is not None for item in batched_results):
_batched_supported = True
_dbg("path:batched")
return batched_results # type: ignore[return-value]
except Exception:
_batched_supported = False
if _cute_supported is not False and _use_cute_for_sizes(problem_sizes):
try:
compiled_func = compile_kernel()
result_tensors = []
for (a_ref, b_ref, c_ref), (sfa_reordered, sfb_reordered), (m, n, k, l) in zip(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
):
if l != 1:
raise RuntimeError("cute path only supports l=1")
a_ptr = make_ptr(
ab_dtype,
a_ref.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
b_ptr = make_ptr(
ab_dtype,
b_ref.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
c_ptr = make_ptr(
c_dtype,
c_ref.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=16,
)
sfa_ptr = make_ptr(
sf_dtype,
sfa_reordered.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
sfb_ptr = make_ptr(
sf_dtype,
sfb_reordered.data_ptr(),
cute.AddressSpace.gmem,
assumed_align=32,
)
compiled_func(a_ptr, b_ptr, sfa_ptr, sfb_ptr, c_ptr, (m, n, k, l))
result_tensors.append(c_ref)
_cute_supported = True
_dbg("path:cute")
return result_tensors
except Exception:
_cute_supported = False
result_tensors = []
def _emit_from_2d(out, grouped):
local_results = []
offset = 0
for (_, _, c_ref), m_pad, m in zip(
abc_tensors, grouped["m_pads"], grouped["m_actual"]
):
c_ref[:, :, 0].copy_(out[offset : offset + m, :])
offset += m_pad
local_results.append(c_ref)
return local_results
def _emit_from_3d(out, grouped_pad):
local_results = []
for idx, (_, _, c_ref) in enumerate(abc_tensors):
c_ref[:, :, 0].copy_(out[idx, : grouped_pad["m_actual"][idx], :])
local_results.append(c_ref)
return local_results
grouped_mm_v2 = _get_grouped_mm_v2() if _use_v2 else None
block_scale, global_scale, block_swizzle, global_swizzle = _get_scale_swizzle()
k_val = problem_sizes[0][2] if problem_sizes else 0
strategy_key = tuple(problem_sizes)
strategy = _grouped_strategy_cache.get(strategy_key)
if (
grouped_mm_v2 is not None
and block_scale is not None
and block_swizzle is not None
and strategy != "skip_v2"
):
try:
grouped2d = None
grouped3d = None
if strategy is not None:
attempts = [strategy]
else:
if global_scale is not None:
attempts = [
"3d:stack:kn:scalar",
"3d:stack:kn:group",
"2d:concat:kn:scalar",
"2d:concat:kn:group",
"2d:stack:kn:scalar",
"2d:stack:kn:group",
]
else:
attempts = [
"3d:stack:kn:none",
"2d:concat:kn:none",
"2d:stack:kn:none",
]
for attempt in attempts:
try:
mode, scale_layout, b_layout, global_layout = attempt.split(":")
except ValueError:
mode, scale_layout, b_layout, global_layout = "2d", "stack", "nk", "group"
if mode == "3d":
if grouped3d is None:
grouped3d = _build_grouped_inputs_padded(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
)
grouped = grouped3d
a = grouped["a"]
b = grouped["b_nk"] if b_layout == "nk" else _get_b_kn(grouped)
scale_a_block = grouped["scale_a"]
scale_b_block = grouped["scale_b"]
offs = None
offs_cpu = None
emit = _emit_from_3d
else:
if grouped2d is None:
grouped2d = _build_grouped_inputs(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
)
grouped = grouped2d
a = grouped["a"]
scale_a_concat = grouped["scale_a"]
scale_a_stack = grouped["scale_a_stack"]
if scale_layout == "stack":
if scale_a_stack is None:
scale_a_stack = _get_scale_a_stack(grouped)
scale_a_block = scale_a_stack if scale_a_stack is not None else scale_a_concat
else:
scale_a_block = scale_a_concat
scale_b_block = grouped["scale_b"]
b = grouped["b_nk"] if b_layout == "nk" else _get_b_kn(grouped)
offs = grouped["offs"]
offs_cpu = grouped["offs_cpu"]
emit = _emit_from_2d
if scale_a_block is None or scale_b_block is None:
continue
if global_scale is not None and global_layout != "none":
per_group = global_layout == "group"
global_a = _get_global_scale(len(abc_tensors), a.device, per_group)
global_b = _get_global_scale(len(abc_tensors), a.device, per_group)
scale_a = [scale_a_block, global_a]
scale_b = [scale_b_block, global_b]
scale_recipe_a = [_enum_to_int(block_scale), _enum_to_int(global_scale)]
scale_recipe_b = [_enum_to_int(block_scale), _enum_to_int(global_scale)]
swizzle_a = [
_enum_to_int(block_swizzle),
_enum_to_int(global_swizzle if global_swizzle is not None else block_swizzle),
]
swizzle_b = [
_enum_to_int(block_swizzle),
_enum_to_int(global_swizzle if global_swizzle is not None else block_swizzle),
]
else:
scale_a = scale_a_block
scale_b = scale_b_block
scale_recipe_a = _enum_to_int(block_scale)
scale_recipe_b = _enum_to_int(block_scale)
swizzle_a = _enum_to_int(block_swizzle)
swizzle_b = _enum_to_int(block_swizzle)
out = _try_scaled_grouped_mm_v2(
grouped_mm_v2,
a,
b,
scale_a,
scale_recipe_a,
scale_b,
scale_recipe_b,
swizzle_a,
swizzle_b,
offs,
offs_cpu,
torch.float16,
)
if out is None:
out = _try_scaled_grouped_mm_v2(
grouped_mm_v2,
a,
b,
scale_a,
scale_recipe_a,
scale_b,
scale_recipe_b,
swizzle_a,
swizzle_b,
offs,
offs_cpu,
torch.bfloat16,
)
if out is not None:
out = out.to(torch.float16)
if out is not None:
_grouped_strategy_cache[strategy_key] = attempt
result_tensors = emit(out, grouped)
_dbg("path:v2:" + attempt)
return result_tensors
_grouped_strategy_cache[strategy_key] = "skip_v2"
except Exception:
_grouped_strategy_cache[strategy_key] = "skip_v2"
grouped_mm = _get_grouped_mm()
strategy_v1 = _grouped_strategy_cache_v1.get(strategy_key)
if grouped_mm is not None and strategy_v1 != "skip_v1":
try:
grouped = _build_grouped_inputs(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
)
a = grouped["a"]
scale_a_block = grouped["scale_a"]
scale_b_block = grouped["scale_b"]
b_nk = grouped["b_nk"]
b_kn = grouped["b_kn"]
attempts = [strategy_v1] if strategy_v1 is not None else ["nk", "kn"]
for attempt in attempts:
b = b_nk if attempt == "nk" else b_kn
out = _try_scaled_grouped_mm_v1(
grouped_mm,
a,
b,
scale_a_block,
scale_b_block,
grouped["offs"],
grouped["offs_cpu"],
torch.float16,
)
if out is None:
out = _try_scaled_grouped_mm_v1(
grouped_mm,
a,
b,
scale_a_block,
scale_b_block,
grouped["offs"],
grouped["offs_cpu"],
torch.bfloat16,
)
if out is not None:
out = out.to(torch.float16)
if out is not None:
_grouped_strategy_cache_v1[strategy_key] = attempt
result_tensors = _emit_from_2d(out, grouped)
_dbg("path:v1:" + attempt)
return result_tensors
_grouped_strategy_cache_v1[strategy_key] = "skip_v1"
except Exception:
_grouped_strategy_cache_v1[strategy_key] = "skip_v1"
for (a_ref, b_ref, c_ref), (sfa_reordered, sfb_reordered), (_, _, _, l) in zip(
abc_tensors, sfasfb_reordered_tensors, problem_sizes
):
for l_idx in range(l):
scale_a = blocked_from_reordered(sfa_reordered, l_idx)
scale_b = blocked_from_reordered(sfb_reordered, l_idx)
mat_a = a_ref[:, :, l_idx].view(torch.float4_e2m1fn_x2)
mat_b = b_ref[:, :, l_idx].transpose(0, 1).view(torch.float4_e2m1fn_x2)
_scaled_mm_out(
mat_a,
mat_b,
scale_a,
scale_b,
c_ref[:, :, l_idx],
)
result_tensors.append(c_ref)
_dbg("path:per_group")
return result_tensors
scrolls · 1959 lines total
Source code from GPU Mode and the KernelBot dataset · June 9 Researcher Reciprocity License v1.0
Changes from previous submission
Against this author's previous submission submission 408508.
⋯ 711 unchanged linespass_CPP_B64 = "CiAgICAjaW5jbHVkZSA8dG9yY2gvZXh0ZW5zaW9uLmg+CiAgICAjaW5jbHVkZSA8dmVjdG9yPgoKICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IGdyb3VwZWRfZ2VtbV8xc20oCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKTsKCiAgICBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiBncm91cGVkX2dlbW1fMnNtKAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBBLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBCLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBDLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBTRkEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQik7CiAgICA="- _CU_B64 = "CiAgICAjaW5jbHVkZSA8dG9yY2gvZXh0ZW5zaW9uLmg+CiAgICAjaW5jbHVkZSA8QVRlbi9jdWRhL0NVREFDb250ZXh0Lmg+CiAgICAjaW5jbHVkZSA8YzEwL2N1ZGEvQ1VEQVN0cmVhbS5oPgogICAgI2luY2x1ZGUgPGMxMC9jdWRhL0NVREFHdWFyZC5oPgogICAgI2luY2x1ZGUgPHZlY3Rvcj4KICAgICNpbmNsdWRlIDxzdGRleGNlcHQ+CgogICAgI2lmZGVmIF9fQ1VEQV9OT19IQUxGX09QRVJBVE9SU19fCiAgICAjdW5kZWYgX19DVURBX05PX0hBTEZfT1BFUkFUT1JTX18KICAgICNlbmRpZgogICAgI2lmZGVmIF9fQ1VEQV9OT19IQUxGX0NPTlZFUlNJT05TX18KICAgICN1bmRlZiBfX0NVREFfTk9fSEFMRl9DT05WRVJTSU9OU19fCiAgICAjZW5kaWYKICAgICNpZmRlZiBfX0NVREFfTk9fSEFMRjJfT1BFUkFUT1JTX18KICAgICN1bmRlZiBfX0NVREFfTk9fSEFMRjJfT1BFUkFUT1JTX18KICAgICNlbmRpZgoKICAgICNpbmNsdWRlIDxjdWRhX3J1bnRpbWUuaD4KICAgICNpZiBkZWZpbmVkKF9fQ1VEQV9BUkNIX18pICYmICFkZWZpbmVkKENVVExBU1NfQVJDSF9NTUFfU00xMDBBX0VOQUJMRUQpCiAgICAjZGVmaW5lIENVVExBU1NfQVJDSF9NTUFfU00xMDBBX0VOQUJMRUQgMQogICAgI2VuZGlmCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9jdXRsYXNzLmgiCiAgICAjaW5jbHVkZSAiY3V0ZS90ZW5zb3IuaHBwIgogICAgI2luY2x1ZGUgImN1dGxhc3MvdGVuc29yX3JlZi5oIgogICAgI2luY2x1ZGUgImN1dGxhc3MvZXBpbG9ndWUvY29sbGVjdGl2ZS9kZWZhdWx0X2VwaWxvZ3VlLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2VwaWxvZ3VlL3RocmVhZC9saW5lYXJfY29tYmluYXRpb24uaCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZGlzcGF0Y2hfcG9saWN5LmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZ3JvdXBfYXJyYXlfcHJvYmxlbV9zaGFwZS5ocHAiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9nZW1tL2NvbGxlY3RpdmUvY29sbGVjdGl2ZV9idWlsZGVyLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2VwaWxvZ3VlL2NvbGxlY3RpdmUvY29sbGVjdGl2ZV9idWlsZGVyLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZGV2aWNlL2dlbW1fdW5pdmVyc2FsX2FkYXB0ZXIuaCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0va2VybmVsL2dlbW1fdW5pdmVyc2FsLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL3V0aWwvcGFja2VkX3N0cmlkZS5ocHAiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9rZXJuZWxfaGFyZHdhcmVfaW5mby5oIgogICAgI2luY2x1ZGUgImN1dGxhc3MvdXRpbC9kZXZpY2VfbWVtb3J5LmgiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9nZW1tL2tlcm5lbC90aWxlX3NjaGVkdWxlcl9wYXJhbXMuaCIKCiAgICAjZGVmaW5lIENVVExBU1NfQ0hFQ0soc3RhdHVzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICBkbyB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICBjdXRsYXNzOjpTdGF0dXMgX3N0YXR1cyA9IChzdGF0dXMpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgICAgaWYgKF9zdGF0dXMgIT0gY3V0bGFzczo6U3RhdHVzOjprU3VjY2VzcykgeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICAgICAgdGhyb3cgc3RkOjpydW50aW1lX2Vycm9yKCJDVVRMQVNTIGVycm9yIik7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgIH0gd2hpbGUgKDApCgogICAgI2RlZmluZSBDVURBX0NIRUNLKGV4cHIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICBkbyB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICBjdWRhRXJyb3JfdCBfZXJyID0gKGV4cHIpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgICAgaWYgKF9lcnIgIT0gY3VkYVN1Y2Nlc3MpIHsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICAgICAgdGhyb3cgc3RkOjpydW50aW1lX2Vycm9yKGN1ZGFHZXRFcnJvclN0cmluZyhfZXJyKSk7ICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgIH0gd2hpbGUgKDApCgogICAgdXNpbmcgUHJvYmxlbVNoYXBlID0gY3V0bGFzczo6Z2VtbTo6R3JvdXBQcm9ibGVtU2hhcGU8Y3V0ZTo6U2hhcGU8aW50LGludCxpbnQ+PjsKICAgIHVzaW5nIEVsZW1lbnRJbnB1dCA9IGN1dGxhc3M6OmZsb2F0X2UybTFfdDsKICAgIHVzaW5nIEVsZW1lbnRTRiAgICA9IGN1dGxhc3M6OmZsb2F0X3VlNG0zX3Q7CiAgICB1c2luZyBFbGVtZW50QyAgICAgPSBjdXRsYXNzOjpoYWxmX3Q7CgogICAgdXNpbmcgRWxlbWVudEEgPSBjdXRsYXNzOjpudl9mbG9hdDRfdDxFbGVtZW50SW5wdXQ+OwogICAgdXNpbmcgTGF5b3V0QSAgPSBjdXRsYXNzOjpsYXlvdXQ6OlJvd01ham9yOwogICAgY29uc3RleHByIGludCBBbGlnbm1lbnRBICA9IDMyOwoKICAgIHVzaW5nIEVsZW1lbnRCID0gY3V0bGFzczo6bnZfZmxvYXQ0X3Q8RWxlbWVudElucHV0PjsKICAgIHVzaW5nIExheW91dEIgID0gY3V0bGFzczo6bGF5b3V0OjpDb2x1bW5NYWpvcjsKICAgIGNvbnN0ZXhwciBpbnQgQWxpZ25tZW50QiAgPSAzMjsKCiAgICB1c2luZyBFbGVtZW50RCA9IEVsZW1lbnRDOwogICAgdXNpbmcgTGF5b3V0QyAgPSBjdXRsYXNzOjpsYXlvdXQ6OlJvd01ham9yOwogICAgY29uc3RleHByIGludCBBbGlnbm1lbnRDICA9IDEyOCAvIGN1dGxhc3M6OnNpemVvZl9iaXRzPEVsZW1lbnRDPjo6dmFsdWU7CiAgICBjb25zdGV4cHIgaW50IEFsaWdubWVudEQgID0gMTI4IC8gY3V0bGFzczo6c2l6ZW9mX2JpdHM8RWxlbWVudEQ+Ojp2YWx1ZTsKICAgIHVzaW5nIEVsZW1lbnRBY2N1bXVsYXRvciAgPSBmbG9hdDsKCiAgICB1c2luZyBBcmNoVGFnID0gY3V0bGFzczo6YXJjaDo6U20xMDA7CiAgICB1c2luZyBPcGVyYXRvckNsYXNzID0gY3V0bGFzczo6YXJjaDo6T3BDbGFzc0Jsb2NrU2NhbGVkVGVuc29yT3A7CiAgICB1c2luZyBTdGFnZUNvdW50VHlwZSA9IGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OlN0YWdlQ291bnRBdXRvOwoKICAgIHN0cnVjdCBNTUExU01Db25maWcgewogICAgICB1c2luZyBNbWFUaWxlU2hhcGUgICAgID0gY3V0ZTo6U2hhcGU8Y3V0ZTo6XzEyOCxjdXRlOjpfNTEyLGN1dGU6Ol8yNTY+OwogICAgICB1c2luZyBDbHVzdGVyU2hhcGUgICAgID0gY3V0ZTo6U2hhcGU8Y3V0ZTo6XzEsY3V0ZTo6XzEsY3V0ZTo6XzE+OwogICAgICB1c2luZyBLZXJuZWxTY2hlZHVsZSAgID0gY3V0bGFzczo6Z2VtbTo6S2VybmVsUHRyQXJyYXlUbWFXYXJwU3BlY2lhbGl6ZWQxU21OdmY0U20xMDA7CiAgICAgIHVzaW5nIEVwaWxvZ3VlU2NoZWR1bGUgPSBjdXRsYXNzOjplcGlsb2d1ZTo6UHRyQXJyYXlUbWFXYXJwU3BlY2lhbGl6ZWQxU207CiAgICB9OwoKICAgIHN0cnVjdCBNTUEyU01Db25maWcgewogICAgICB1c2luZyBNbWFUaWxlU2hhcGUgICAgID0gY3V0ZTo6U2hhcGU8Y3V0ZTo6XzI1NixjdXRlOjpfMjU2LGN1dGU6Ol8yNTY+OwogICAgICB1c2luZyBDbHVzdGVyU2hhcGUgICAgID0gY3V0ZTo6U2hhcGU8Y3V0ZTo6XzIsY3V0ZTo6XzEsY3V0ZTo6XzE+OwogICAgICB1c2luZyBLZXJuZWxTY2hlZHVsZSAgID0gY3V0bGFzczo6Z2VtbTo6S2VybmVsUHRyQXJyYXlUbWFXYXJwU3BlY2lhbGl6ZWQyU21OdmY0U20xMDA7CiAgICAgIHVzaW5nIEVwaWxvZ3VlU2NoZWR1bGUgPSBjdXRsYXNzOjplcGlsb2d1ZTo6UHRyQXJyYXlUbWFXYXJwU3BlY2lhbGl6ZWQyU207CiAgICB9OwoKICAgIHVzaW5nIENvbGxlY3RpdmVFcGlsb2d1ZTFTTSA9IHR5cGVuYW1lIGN1dGxhc3M6OmVwaWxvZ3VlOjpjb2xsZWN0aXZlOjpDb2xsZWN0aXZlQnVpbGRlcjwKICAgICAgICBBcmNoVGFnLCBPcGVyYXRvckNsYXNzLAogICAgICAgIHR5cGVuYW1lIE1NQTFTTUNvbmZpZzo6TW1hVGlsZVNoYXBlLCB0eXBlbmFtZSBNTUExU01Db25maWc6OkNsdXN0ZXJTaGFwZSwKICAgICAgICBjdXRlOjpTaGFwZTxjdXRlOjpfMTI4LGN1dGU6Ol82ND4sCiAgICAgICAgRWxlbWVudEFjY3VtdWxhdG9yLCBFbGVtZW50QWNjdW11bGF0b3IsCiAgICAgICAgRWxlbWVudEMsIExheW91dEMgKiwgQWxpZ25tZW50QywKICAgICAgICBFbGVtZW50RCwgTGF5b3V0QyAqLCBBbGlnbm1lbnRELAogICAgICAgIHR5cGVuYW1lIE1NQTFTTUNvbmZpZzo6RXBpbG9ndWVTY2hlZHVsZQogICAgPjo6Q29sbGVjdGl2ZU9wOwoKICAgIHVzaW5nIENvbGxlY3RpdmVNYWlubG9vcDFTTSA9IHR5cGVuYW1lIGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OkNvbGxlY3RpdmVCdWlsZGVyPAogICAgICBBcmNoVGFnLCBPcGVyYXRvckNsYXNzLAogICAgICBFbGVtZW50QSwgTGF5b3V0QSAqLCBBbGlnbm1lbnRBLAogICAgICBFbGVtZW50QiwgTGF5b3V0QiAqLCBBbGlnbm1lbnRCLAogICAgICBFbGVtZW50QWNjdW11bGF0b3IsCiAgICAgIHR5cGVuYW1lIE1NQTFTTUNvbmZpZzo6TW1hVGlsZVNoYXBlLCB0eXBlbmFtZSBNTUExU01Db25maWc6OkNsdXN0ZXJTaGFwZSwKICAgICAgY3V0bGFzczo6Z2VtbTo6Y29sbGVjdGl2ZTo6U3RhZ2VDb3VudEF1dG9DYXJ2ZW91dDwKICAgICAgICBzdGF0aWNfY2FzdDxpbnQ+KHNpemVvZih0eXBlbmFtZSBDb2xsZWN0aXZlRXBpbG9ndWUxU006OlNoYXJlZFN0b3JhZ2UpKT4sCiAgICAgIHR5cGVuYW1lIE1NQTFTTUNvbmZpZzo6S2VybmVsU2NoZWR1bGUKICAgID46OkNvbGxlY3RpdmVPcDsKCiAgICB1c2luZyBHZW1tS2VybmVsMVNNID0gY3V0bGFzczo6Z2VtbTo6a2VybmVsOjpHZW1tVW5pdmVyc2FsPAogICAgICAgIFByb2JsZW1TaGFwZSwKICAgICAgICBDb2xsZWN0aXZlTWFpbmxvb3AxU00sCiAgICAgICAgQ29sbGVjdGl2ZUVwaWxvZ3VlMVNNCiAgICA+OwogICAgdXNpbmcgR2VtbTFTTSA9IGN1dGxhc3M6OmdlbW06OmRldmljZTo6R2VtbVVuaXZlcnNhbEFkYXB0ZXI8R2VtbUtlcm5lbDFTTT47CgogICAgdXNpbmcgQ29sbGVjdGl2ZUVwaWxvZ3VlMlNNID0gdHlwZW5hbWUgY3V0bGFzczo6ZXBpbG9ndWU6OmNvbGxlY3RpdmU6OkNvbGxlY3RpdmVCdWlsZGVyPAogICAgICAgIEFyY2hUYWcsIE9wZXJhdG9yQ2xhc3MsCiAgICAgICAgdHlwZW5hbWUgTU1BMlNNQ29uZmlnOjpNbWFUaWxlU2hhcGUsIHR5cGVuYW1lIE1NQTJTTUNvbmZpZzo6Q2x1c3RlclNoYXBlLAogICAgICAgIGN1dGU6OlNoYXBlPGN1dGU6Ol8xMjgsY3V0ZTo6XzY0PiwKICAgICAgICBFbGVtZW50QWNjdW11bGF0b3IsIEVsZW1lbnRBY2N1bXVsYXRvciwKICAgICAgICBFbGVtZW50QywgTGF5b3V0QyAqLCBBbGlnbm1lbnRDLAogICAgICAgIEVsZW1lbnRELCBMYXlvdXRDICosIEFsaWdubWVudEQsCiAgICAgICAgdHlwZW5hbWUgTU1BMlNNQ29uZmlnOjpFcGlsb2d1ZVNjaGVkdWxlCiAgICA+OjpDb2xsZWN0aXZlT3A7CgogICAgdXNpbmcgQ29sbGVjdGl2ZU1haW5sb29wMlNNID0gdHlwZW5hbWUgY3V0bGFzczo6Z2VtbTo6Y29sbGVjdGl2ZTo6Q29sbGVjdGl2ZUJ1aWxkZXI8CiAgICAgIEFyY2hUYWcsIE9wZXJhdG9yQ2xhc3MsCiAgICAgIEVsZW1lbnRBLCBMYXlvdXRBICosIEFsaWdubWVudEEsCiAgICAgIEVsZW1lbnRCLCBMYXlvdXRCICosIEFsaWdubWVudEIsCiAgICAgIEVsZW1lbnRBY2N1bXVsYXRvciwKICAgICAgdHlwZW5hbWUgTU1BMlNNQ29uZmlnOjpNbWFUaWxlU2hhcGUsIHR5cGVuYW1lIE1NQTJTTUNvbmZpZzo6Q2x1c3RlclNoYXBlLAogICAgICBjdXRsYXNzOjpnZW1tOjpjb2xsZWN0aXZlOjpTdGFnZUNvdW50QXV0b0NhcnZlb3V0PAogICAgICAgIHN0YXRpY19jYXN0PGludD4oc2l6ZW9mKHR5cGVuYW1lIENvbGxlY3RpdmVFcGlsb2d1ZTJTTTo6U2hhcmVkU3RvcmFnZSkpPiwKICAgICAgdHlwZW5hbWUgTU1BMlNNQ29uZmlnOjpLZXJuZWxTY2hlZHVsZQogICAgPjo6Q29sbGVjdGl2ZU9wOwoKICAgIHVzaW5nIEdlbW1LZXJuZWwyU00gPSBjdXRsYXNzOjpnZW1tOjprZXJuZWw6OkdlbW1Vbml2ZXJzYWw8CiAgICAgICAgUHJvYmxlbVNoYXBlLAogICAgICAgIENvbGxlY3RpdmVNYWlubG9vcDJTTSwKICAgICAgICBDb2xsZWN0aXZlRXBpbG9ndWUyU00KICAgID47CiAgICB1c2luZyBHZW1tMlNNID0gY3V0bGFzczo6Z2VtbTo6ZGV2aWNlOjpHZW1tVW5pdmVyc2FsQWRhcHRlcjxHZW1tS2VybmVsMlNNPjsKCiAgICB0ZW1wbGF0ZSA8dHlwZW5hbWUgVD4KICAgIHN0cnVjdCBQaW5uZWRIb3N0QnVmZmVyIHsKICAgICAgVCogcHRyID0gbnVsbHB0cjsKICAgICAgc2l6ZV90IGNvdW50ID0gMDsKCiAgICAgIFBpbm5lZEhvc3RCdWZmZXIoKSA9IGRlZmF1bHQ7CiAgICAgIGV4cGxpY2l0IFBpbm5lZEhvc3RCdWZmZXIoc2l6ZV90IG4pIHsgYWxsb2NhdGUobik7IH0KCiAgICAgIHZvaWQgYWxsb2NhdGUoc2l6ZV90IG4pIHsKICAgICAgICBpZiAocHRyICYmIGNvdW50ID49IG4pIHsKICAgICAgICAgIHJldHVybjsKICAgICAgICB9CiAgICAgICAgcmVsZWFzZSgpOwogICAgICAgIGNvdW50ID0gbjsKICAgICAgICBDVURBX0NIRUNLKGN1ZGFIb3N0QWxsb2MocmVpbnRlcnByZXRfY2FzdDx2b2lkKio+KCZwdHIpLCBuICogc2l6ZW9mKFQpLCBjdWRhSG9zdEFsbG9jUG9ydGFibGUpKTsKICAgICAgfQoKICAgICAgdm9pZCByZWxlYXNlKCkgewogICAgICAgIGlmIChwdHIpIHsKICAgICAgICAgIGN1ZGFGcmVlSG9zdChwdHIpOwogICAgICAgICAgcHRyID0gbnVsbHB0cjsKICAgICAgICAgIGNvdW50ID0gMDsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIH5QaW5uZWRIb3N0QnVmZmVyKCkgeyByZWxlYXNlKCk7IH0KCiAgICAgIFQqIGRhdGEoKSBjb25zdCB7IHJldHVybiBwdHI7IH0KICAgICAgVCYgb3BlcmF0b3JbXShzaXplX3QgaWR4KSB7IHJldHVybiBwdHJbaWR4XTsgfQogICAgfTsKCiAgICB0ZW1wbGF0ZSA8dHlwZW5hbWUgVD4KICAgIHN0cnVjdCBEZXZpY2VCdWZmZXIgewogICAgICBUKiBwdHIgPSBudWxscHRyOwogICAgICBzaXplX3QgY291bnQgPSAwOwoKICAgICAgRGV2aWNlQnVmZmVyKCkgPSBkZWZhdWx0OwogICAgICBleHBsaWNpdCBEZXZpY2VCdWZmZXIoc2l6ZV90IG4pIHsgYWxsb2NhdGUobik7IH0KCiAgICAgIHZvaWQgYWxsb2NhdGUoc2l6ZV90IG4pIHsKICAgICAgICBpZiAocHRyICYmIGNvdW50ID49IG4pIHsKICAgICAgICAgIHJldHVybjsKICAgICAgICB9CiAgICAgICAgcmVsZWFzZSgpOwogICAgICAgIGNvdW50ID0gbjsKICAgICAgICBDVURBX0NIRUNLKGN1ZGFNYWxsb2MocmVpbnRlcnByZXRfY2FzdDx2b2lkKio+KCZwdHIpLCBuICogc2l6ZW9mKFQpKSk7CiAgICAgIH0KCiAgICAgIHZvaWQgcmVsZWFzZSgpIHsKICAgICAgICBpZiAocHRyKSB7CiAgICAgICAgICBjdWRhRnJlZShwdHIpOwogICAgICAgICAgcHRyID0gbnVsbHB0cjsKICAgICAgICAgIGNvdW50ID0gMDsKICAgICAgICB9CiAgICAgIH0KCiAgICAgIH5EZXZpY2VCdWZmZXIoKSB7IHJlbGVhc2UoKTsgfQoKICAgICAgVCogZ2V0KCkgY29uc3QgeyByZXR1cm4gcHRyOyB9CgogICAgICB2b2lkIGNvcHlfZnJvbV9ob3N0KGNvbnN0IFQqIGhvc3QsIHNpemVfdCBuLCBjdWRhU3RyZWFtX3Qgc3RyZWFtKSB7CiAgICAgICAgQ1VEQV9DSEVDSyhjdWRhTWVtY3B5QXN5bmMocHRyLCBob3N0LCBuICogc2l6ZW9mKFQpLCBjdWRhTWVtY3B5SG9zdFRvRGV2aWNlLCBzdHJlYW0pKTsKICAgICAgfQogICAgfTsKCiAgICB0ZW1wbGF0ZSA8dHlwZW5hbWUgR2VtbSwgaW50IENsdXN0ZXJNLCBpbnQgQ2x1c3Rlck4+CiAgICBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiBydW5fZ3JvdXBlZF9pbXBsKAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBBLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBCLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBDLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBTRkEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQikgewoKICAgICAgVE9SQ0hfQ0hFQ0soQS5zaXplKCkgPT0gQi5zaXplKCksICJBL0Igc2l6ZSBtaXNtYXRjaCIpOwogICAgICBUT1JDSF9DSEVDSyhBLnNpemUoKSA9PSBDLnNpemUoKSwgIkEvQyBzaXplIG1pc21hdGNoIik7CiAgICAgIFRPUkNIX0NIRUNLKEEuc2l6ZSgpID09IFNGQS5zaXplKCksICJBL1NGQSBzaXplIG1pc21hdGNoIik7CiAgICAgIFRPUkNIX0NIRUNLKEEuc2l6ZSgpID09IFNGQi5zaXplKCksICJBL1NGQiBzaXplIG1pc21hdGNoIik7CiAgICAgIGNvbnN0IGludDMyX3QgZ3JvdXBzID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQS5zaXplKCkpOwogICAgICBUT1JDSF9DSEVDSyhncm91cHMgPiAwLCAiTm8gZ3JvdXBzIik7CgogICAgICBpbnQgZGV2aWNlX2lkID0gQVswXS5nZXRfZGV2aWNlKCk7CiAgICAgIGMxMDo6Y3VkYTo6Q1VEQUd1YXJkIGRldmljZV9ndWFyZChkZXZpY2VfaWQpOwoKICAgICAgdXNpbmcgU3RyaWRlQSA9IHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkludGVybmFsU3RyaWRlQTsKICAgICAgdXNpbmcgU3RyaWRlQiA9IHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkludGVybmFsU3RyaWRlQjsKICAgICAgdXNpbmcgU3RyaWRlQyA9IHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkludGVybmFsU3RyaWRlQzsKICAgICAgdXNpbmcgU3RyaWRlRCA9IHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkludGVybmFsU3RyaWRlRDsKICAgICAgdXNpbmcgTGF5b3V0U0ZBID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6Q29sbGVjdGl2ZU1haW5sb29wOjpJbnRlcm5hbExheW91dFNGQTsKICAgICAgdXNpbmcgTGF5b3V0U0ZCID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6Q29sbGVjdGl2ZU1haW5sb29wOjpJbnRlcm5hbExheW91dFNGQjsKICAgICAgdXNpbmcgU20xeHhCbGtTY2FsZWRDb25maWcgPSB0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpDb2xsZWN0aXZlTWFpbmxvb3A6OlNtMXh4QmxrU2NhbGVkQ29uZmlnOwoKICAgICAgc3RhdGljIHNpemVfdCBjYXBhY2l0eSA9IDA7CiAgICAgIHN0YXRpYyBpbnQgY2FjaGVkX2RldmljZSA9IC0xOwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QSBjb25zdCo+IHB0cl9BX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRCIGNvbnN0Kj4gcHRyX0JfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEMgY29uc3QqPiBwdHJfQ19ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFcGlsb2d1ZU91dHB1dE9wOjpFbGVtZW50T3V0cHV0Kj4gcHRyX0RfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8dHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6RWxlbWVudFNGIGNvbnN0Kj4gcHRyX1NGQV9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPiBwdHJfU0ZCX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPFN0cmlkZUE+IHN0cmlkZV9BX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPFN0cmlkZUI+IHN0cmlkZV9CX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPFN0cmlkZUM+IHN0cmlkZV9DX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPFN0cmlkZUQ+IHN0cmlkZV9EX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPExheW91dFNGQT4gbGF5b3V0X1NGQV9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjxMYXlvdXRTRkI+IGxheW91dF9TRkJfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8dHlwZW5hbWUgUHJvYmxlbVNoYXBlOjpVbmRlcmx5aW5nUHJvYmxlbVNoYXBlPiBwcm9ibGVtX3NpemVzX2hvc3Q7CgogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIFByb2JsZW1TaGFwZTo6VW5kZXJseWluZ1Byb2JsZW1TaGFwZT4gcHJvYmxlbV9zaXplc19kZXZpY2U7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEEgY29uc3QqPiBwdHJfQTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QiBjb25zdCo+IHB0cl9COwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkVsZW1lbnRTRiBjb25zdCo+IHB0cl9TRkE7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6RWxlbWVudFNGIGNvbnN0Kj4gcHRyX1NGQjsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QyBjb25zdCo+IHB0cl9DOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIEdlbW06OkVwaWxvZ3VlT3V0cHV0T3A6OkVsZW1lbnRPdXRwdXQqPiBwdHJfRDsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxTdHJpZGVBPiBzdHJpZGVfQTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxTdHJpZGVCPiBzdHJpZGVfQjsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxTdHJpZGVDPiBzdHJpZGVfQzsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxTdHJpZGVEPiBzdHJpZGVfRDsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxMYXlvdXRTRkE+IGxheW91dF9TRkE7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8TGF5b3V0U0ZCPiBsYXlvdXRfU0ZCOwogICAgICBzdGF0aWMgc2l6ZV90IHdvcmtzcGFjZV9zaXplX2NhY2hlZCA9IDA7CiAgICAgIHN0YXRpYyB0b3JjaDo6VGVuc29yIHdvcmtzcGFjZV90ZW5zb3I7CgogICAgICBpZiAoY2FjaGVkX2RldmljZSAhPSBkZXZpY2VfaWQgfHwgY2FwYWNpdHkgPCBzdGF0aWNfY2FzdDxzaXplX3Q+KGdyb3VwcykpIHsKICAgICAgICBjYWNoZWRfZGV2aWNlID0gZGV2aWNlX2lkOwogICAgICAgIGNhcGFjaXR5ID0gc3RhdGljX2Nhc3Q8c2l6ZV90Pihncm91cHMpOwogICAgICAgIHB0cl9BX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQl9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX0NfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9EX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfU0ZBX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfU0ZCX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfQV9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0JfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9DX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfRF9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgbGF5b3V0X1NGQV9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgbGF5b3V0X1NGQl9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHJvYmxlbV9zaXplc19ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CgogICAgICAgIHByb2JsZW1fc2l6ZXNfZGV2aWNlLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX0EuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQi5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkEuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfU0ZCLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX0MuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfRC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9BLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0IuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfQy5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9ELmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgbGF5b3V0X1NGQS5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIGxheW91dF9TRkIuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgfQoKICAgICAgZm9yIChpbnQzMl90IGkgPSAwOyBpIDwgZ3JvdXBzOyArK2kpIHsKICAgICAgICBUT1JDSF9DSEVDSyhBW2ldLmlzX2N1ZGEoKSwgIkEgbXVzdCBiZSBDVURBIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soQltpXS5pc19jdWRhKCksICJCIG11c3QgYmUgQ1VEQSIpOwogICAgICAgIFRPUkNIX0NIRUNLKENbaV0uaXNfY3VkYSgpLCAiQyBtdXN0IGJlIENVREEiKTsKICAgICAgICBUT1JDSF9DSEVDSyhTRkFbaV0uaXNfY3VkYSgpLCAiU0ZBIG11c3QgYmUgQ1VEQSIpOwogICAgICAgIFRPUkNIX0NIRUNLKFNGQltpXS5pc19jdWRhKCksICJTRkIgbXVzdCBiZSBDVURBIik7CgogICAgICAgIGludDMyX3QgbSA9IHN0YXRpY19jYXN0PGludDMyX3Q+KEFbaV0uc2l6ZSgwKSk7CiAgICAgICAgaW50MzJfdCBrX3BhY2tlZCA9IHN0YXRpY19jYXN0PGludDMyX3Q+KEFbaV0uc2l6ZSgxKSk7CiAgICAgICAgaW50MzJfdCBrID0ga19wYWNrZWQgKiAyOwogICAgICAgIGludDMyX3QgbiA9IHN0YXRpY19jYXN0PGludDMyX3Q+KEJbaV0uc2l6ZSgwKSk7CiAgICAgICAgVE9SQ0hfQ0hFQ0soQltpXS5zaXplKDEpICogMiA9PSBrLCAiSyBtaXNtYXRjaCIpOwogICAgICAgIFRPUkNIX0NIRUNLKENbaV0uc2l6ZSgwKSA9PSBtICYmIENbaV0uc2l6ZSgxKSA9PSBuLCAiQyBzaGFwZSBtaXNtYXRjaCIpOwoKICAgICAgICBwcm9ibGVtX3NpemVzX2hvc3RbaV0gPSBjdXRlOjptYWtlX3NoYXBlKG0sIG4sIGspOwogICAgICAgIHN0cmlkZV9BX2hvc3RbaV0gPSBjdXRsYXNzOjptYWtlX2N1dGVfcGFja2VkX3N0cmlkZShTdHJpZGVBe30sIHttLCBrLCAxfSk7CiAgICAgICAgc3RyaWRlX0JfaG9zdFtpXSA9IGN1dGxhc3M6Om1ha2VfY3V0ZV9wYWNrZWRfc3RyaWRlKFN0cmlkZUJ7fSwge24sIGssIDF9KTsKICAgICAgICBzdHJpZGVfQ19ob3N0W2ldID0gY3V0bGFzczo6bWFrZV9jdXRlX3BhY2tlZF9zdHJpZGUoU3RyaWRlQ3t9LCB7bSwgbiwgMX0pOwogICAgICAgIHN0cmlkZV9EX2hvc3RbaV0gPSBjdXRsYXNzOjptYWtlX2N1dGVfcGFja2VkX3N0cmlkZShTdHJpZGVEe30sIHttLCBuLCAxfSk7CiAgICAgICAgbGF5b3V0X1NGQV9ob3N0W2ldID0gU20xeHhCbGtTY2FsZWRDb25maWc6OnRpbGVfYXRvbV90b19zaGFwZV9TRkEoY3V0ZTo6bWFrZV9zaGFwZShtLCBuLCBrLCAxKSk7CiAgICAgICAgbGF5b3V0X1NGQl9ob3N0W2ldID0gU20xeHhCbGtTY2FsZWRDb25maWc6OnRpbGVfYXRvbV90b19zaGFwZV9TRkIoY3V0ZTo6bWFrZV9zaGFwZShtLCBuLCBrLCAxKSk7CgogICAgICAgIHB0cl9BX2hvc3RbaV0gPSByZWludGVycHJldF9jYXN0PHR5cGVuYW1lIEdlbW06OkVsZW1lbnRBIGNvbnN0Kj4oQVtpXS5kYXRhX3B0cigpKTsKICAgICAgICBwdHJfQl9ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QiBjb25zdCo+KEJbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX0NfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEMgY29uc3QqPihDW2ldLmRhdGFfcHRyKCkpOwogICAgICAgIHB0cl9EX2hvc3RbaV0gPSByZWludGVycHJldF9jYXN0PHR5cGVuYW1lIEdlbW06OkVwaWxvZ3VlT3V0cHV0T3A6OkVsZW1lbnRPdXRwdXQqPihDW2ldLmRhdGFfcHRyKCkpOwogICAgICAgIHB0cl9TRkFfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6RWxlbWVudFNGIGNvbnN0Kj4oU0ZBW2ldLmRhdGFfcHRyKCkpOwogICAgICAgIHB0cl9TRkJfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6RWxlbWVudFNGIGNvbnN0Kj4oU0ZCW2ldLmRhdGFfcHRyKCkpOwogICAgICB9CgogICAgICBhdXRvIHN0cmVhbV9vYmogPSBjMTA6OmN1ZGE6OmdldEN1cnJlbnRDVURBU3RyZWFtKGRldmljZV9pZCk7CiAgICAgIGMxMDo6Y3VkYTo6Q1VEQVN0cmVhbUd1YXJkIHN0cmVhbV9ndWFyZChzdHJlYW1fb2JqKTsKICAgICAgY3VkYVN0cmVhbV90IHN0cmVhbSA9IHN0cmVhbV9vYmouc3RyZWFtKCk7CiAgICAgIHByb2JsZW1fc2l6ZXNfZGV2aWNlLmNvcHlfZnJvbV9ob3N0KHByb2JsZW1fc2l6ZXNfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKICAgICAgcHRyX0EuY29weV9mcm9tX2hvc3QocHRyX0FfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKICAgICAgcHRyX0IuY29weV9mcm9tX2hvc3QocHRyX0JfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKICAgICAgcHRyX1NGQS5jb3B5X2Zyb21faG9zdChwdHJfU0ZBX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHB0cl9TRkIuY29weV9mcm9tX2hvc3QocHRyX1NGQl9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfQy5jb3B5X2Zyb21faG9zdChwdHJfQ19ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfRC5jb3B5X2Zyb21faG9zdChwdHJfRF9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBzdHJpZGVfQS5jb3B5X2Zyb21faG9zdChzdHJpZGVfQV9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBzdHJpZGVfQi5jb3B5X2Zyb21faG9zdChzdHJpZGVfQl9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBzdHJpZGVfQy5jb3B5X2Zyb21faG9zdChzdHJpZGVfQ19ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBzdHJpZGVfRC5jb3B5X2Zyb21faG9zdChzdHJpZGVfRF9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBsYXlvdXRfU0ZBLmNvcHlfZnJvbV9ob3N0KGxheW91dF9TRkFfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKICAgICAgbGF5b3V0X1NGQi5jb3B5X2Zyb21faG9zdChsYXlvdXRfU0ZCX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CgogICAgICBjdXRsYXNzOjpLZXJuZWxIYXJkd2FyZUluZm8gaHdfaW5mbzsKICAgICAgaHdfaW5mby5kZXZpY2VfaWQgPSBkZXZpY2VfaWQ7CiAgICAgIGh3X2luZm8uc21fY291bnQgPSBjdXRsYXNzOjpLZXJuZWxIYXJkd2FyZUluZm86OnF1ZXJ5X2RldmljZV9tdWx0aXByb2Nlc3Nvcl9jb3VudChod19pbmZvLmRldmljZV9pZCk7CiAgICAgIGh3X2luZm8uY2x1c3Rlcl9zaGFwZSA9IGRpbTMoQ2x1c3Rlck0sIENsdXN0ZXJOLCAxKTsKICAgICAgaHdfaW5mby5jbHVzdGVyX3NoYXBlX2ZhbGxiYWNrID0gZGltMyhDbHVzdGVyTSwgQ2x1c3Rlck4sIDEpOwoKICAgICAgdHlwZW5hbWUgR2VtbTo6QXJndW1lbnRzIGFyZ3VtZW50czsKICAgICAgZGVjbHR5cGUoYXJndW1lbnRzLmVwaWxvZ3VlLnRocmVhZCkgZnVzaW9uX2FyZ3M7CiAgICAgIGZ1c2lvbl9hcmdzLmFscGhhX3B0ciA9IG51bGxwdHI7CiAgICAgIGZ1c2lvbl9hcmdzLmJldGFfcHRyID0gbnVsbHB0cjsKICAgICAgZnVzaW9uX2FyZ3MuYWxwaGEgPSBFbGVtZW50QWNjdW11bGF0b3IoMSk7CiAgICAgIGZ1c2lvbl9hcmdzLmJldGEgPSBFbGVtZW50QWNjdW11bGF0b3IoMCk7CiAgICAgIGZ1c2lvbl9hcmdzLmFscGhhX3B0cl9hcnJheSA9IG51bGxwdHI7CiAgICAgIGZ1c2lvbl9hcmdzLmJldGFfcHRyX2FycmF5ID0gbnVsbHB0cjsKICAgICAgZnVzaW9uX2FyZ3MuZEFscGhhID0ge2N1dGU6Ol8we30sIGN1dGU6Ol8we30sIDB9OwogICAgICBmdXNpb25fYXJncy5kQmV0YSA9IHtjdXRlOjpfMHt9LCBjdXRlOjpfMHt9LCAwfTsKCiAgICAgIHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OlRpbGVTY2hlZHVsZXJBcmd1bWVudHMgc2NoZWR1bGVyOwogICAgICBzY2hlZHVsZXIucmFzdGVyX29yZGVyID0gY3V0bGFzczo6Z2VtbTo6a2VybmVsOjpkZXRhaWw6OlJhc3Rlck9yZGVyT3B0aW9uczo6QWxvbmdOOwoKICAgICAgYXJndW1lbnRzID0gdHlwZW5hbWUgR2VtbTo6QXJndW1lbnRzIHsKICAgICAgICBjdXRsYXNzOjpnZW1tOjpHZW1tVW5pdmVyc2FsTW9kZTo6a0dyb3VwZWQsCiAgICAgICAge2dyb3VwcywgcHJvYmxlbV9zaXplc19kZXZpY2UuZ2V0KCksIG51bGxwdHJ9LAogICAgICAgIHtwdHJfQS5nZXQoKSwgc3RyaWRlX0EuZ2V0KCksIHB0cl9CLmdldCgpLCBzdHJpZGVfQi5nZXQoKSwKICAgICAgICAgcHRyX1NGQS5nZXQoKSwgbGF5b3V0X1NGQS5nZXQoKSwgcHRyX1NGQi5nZXQoKSwgbGF5b3V0X1NGQi5nZXQoKX0sCiAgICAgICAge2Z1c2lvbl9hcmdzLCBwdHJfQy5nZXQoKSwgc3RyaWRlX0MuZ2V0KCksIHB0cl9ELmdldCgpLCBzdHJpZGVfRC5nZXQoKX0sCiAgICAgICAgaHdfaW5mbywgc2NoZWR1bGVyCiAgICAgIH07CgogICAgICBHZW1tIGdlbW07CiAgICAgIHNpemVfdCB3b3Jrc3BhY2Vfc2l6ZSA9IEdlbW06OmdldF93b3Jrc3BhY2Vfc2l6ZShhcmd1bWVudHMpOwogICAgICBpZiAoIXdvcmtzcGFjZV90ZW5zb3IuZGVmaW5lZCgpIHx8IGNhY2hlZF9kZXZpY2UgIT0gZGV2aWNlX2lkIHx8IHdvcmtzcGFjZV9zaXplX2NhY2hlZCA8IHdvcmtzcGFjZV9zaXplKSB7CiAgICAgICAgYXV0byBvcHRzID0gdG9yY2g6OlRlbnNvck9wdGlvbnMoKS5kZXZpY2UodG9yY2g6OmtDVURBLCBkZXZpY2VfaWQpLmR0eXBlKHRvcmNoOjprVUludDgpOwogICAgICAgIHdvcmtzcGFjZV90ZW5zb3IgPSB0b3JjaDo6ZW1wdHkoe3N0YXRpY19jYXN0PGxvbmc+KHdvcmtzcGFjZV9zaXplKX0sIG9wdHMpOwogICAgICAgIHdvcmtzcGFjZV9zaXplX2NhY2hlZCA9IHdvcmtzcGFjZV9zaXplOwogICAgICB9CiAgICAgIHZvaWQqIHdvcmtzcGFjZV9wdHIgPSB3b3Jrc3BhY2Vfc2l6ZSA/IHdvcmtzcGFjZV90ZW5zb3IuZGF0YV9wdHIoKSA6IG51bGxwdHI7CgogICAgICBDVVRMQVNTX0NIRUNLKGdlbW0uY2FuX2ltcGxlbWVudChhcmd1bWVudHMpKTsKICAgICAgQ1VUTEFTU19DSEVDSyhnZW1tLmluaXRpYWxpemUoYXJndW1lbnRzLCB3b3Jrc3BhY2VfcHRyLCBzdHJlYW0pKTsKICAgICAgQ1VUTEFTU19DSEVDSyhnZW1tLnJ1bihzdHJlYW0pKTsKCiAgICAgIHJldHVybiBDOwogICAgfQoKICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IGdyb3VwZWRfZ2VtbV8xc20oCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKSB7CiAgICAgIHJldHVybiBydW5fZ3JvdXBlZF9pbXBsPEdlbW0xU00sIDIsIDE+KEEsIEIsIEMsIFNGQSwgU0ZCKTsKICAgIH0KCiAgICBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiBncm91cGVkX2dlbW1fMnNtKAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBBLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBCLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBDLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBTRkEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQikgewogICAgICByZXR1cm4gcnVuX2dyb3VwZWRfaW1wbDxHZW1tMlNNLCAyLCAxPihBLCBCLCBDLCBTRkEsIFNGQik7CiAgICB9CiAgICAK"+ _CU_B64 = "CiAgICAjaW5jbHVkZSA8dG9yY2gvZXh0ZW5zaW9uLmg+CiAgICAjaW5jbHVkZSA8QVRlbi9jdWRhL0NVREFDb250ZXh0Lmg+CiAgICAjaW5jbHVkZSA8YzEwL2N1ZGEvQ1VEQVN0cmVhbS5oPgogICAgI2luY2x1ZGUgPGMxMC9jdWRhL0NVREFHdWFyZC5oPgogICAgI2luY2x1ZGUgPHZlY3Rvcj4KICAgICNpbmNsdWRlIDxzdGRleGNlcHQ+CgogICAgI2lmZGVmIF9fQ1VEQV9OT19IQUxGX09QRVJBVE9SU19fCiAgICAjdW5kZWYgX19DVURBX05PX0hBTEZfT1BFUkFUT1JTX18KICAgICNlbmRpZgogICAgI2lmZGVmIF9fQ1VEQV9OT19IQUxGX0NPTlZFUlNJT05TX18KICAgICN1bmRlZiBfX0NVREFfTk9fSEFMRl9DT05WRVJTSU9OU19fCiAgICAjZW5kaWYKICAgICNpZmRlZiBfX0NVREFfTk9fSEFMRjJfT1BFUkFUT1JTX18KICAgICN1bmRlZiBfX0NVREFfTk9fSEFMRjJfT1BFUkFUT1JTX18KICAgICNlbmRpZgoKICAgICNpbmNsdWRlIDxjdWRhX3J1bnRpbWUuaD4KICAgICNpZiBkZWZpbmVkKF9fQ1VEQV9BUkNIX18pICYmICFkZWZpbmVkKENVVExBU1NfQVJDSF9NTUFfU00xMDBBX0VOQUJMRUQpCiAgICAjZGVmaW5lIENVVExBU1NfQVJDSF9NTUFfU00xMDBBX0VOQUJMRUQgMQogICAgI2VuZGlmCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9jdXRsYXNzLmgiCiAgICAjaW5jbHVkZSAiY3V0ZS90ZW5zb3IuaHBwIgogICAgI2luY2x1ZGUgImN1dGxhc3MvdGVuc29yX3JlZi5oIgogICAgI2luY2x1ZGUgImN1dGxhc3MvZXBpbG9ndWUvY29sbGVjdGl2ZS9kZWZhdWx0X2VwaWxvZ3VlLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2VwaWxvZ3VlL3RocmVhZC9saW5lYXJfY29tYmluYXRpb24uaCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZGlzcGF0Y2hfcG9saWN5LmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZ3JvdXBfYXJyYXlfcHJvYmxlbV9zaGFwZS5ocHAiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9nZW1tL2NvbGxlY3RpdmUvY29sbGVjdGl2ZV9idWlsZGVyLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2VwaWxvZ3VlL2NvbGxlY3RpdmUvY29sbGVjdGl2ZV9idWlsZGVyLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0vZGV2aWNlL2dlbW1fdW5pdmVyc2FsX2FkYXB0ZXIuaCIKICAgICNpbmNsdWRlICJjdXRsYXNzL2dlbW0va2VybmVsL2dlbW1fdW5pdmVyc2FsLmhwcCIKICAgICNpbmNsdWRlICJjdXRsYXNzL3V0aWwvcGFja2VkX3N0cmlkZS5ocHAiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9rZXJuZWxfaGFyZHdhcmVfaW5mby5oIgogICAgI2luY2x1ZGUgImN1dGxhc3MvdXRpbC9kZXZpY2VfbWVtb3J5LmgiCiAgICAjaW5jbHVkZSAiY3V0bGFzcy9nZW1tL2tlcm5lbC90aWxlX3NjaGVkdWxlcl9wYXJhbXMuaCIKCiAgICAjZGVmaW5lIENVVExBU1NfQ0hFQ0soc3RhdHVzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICBkbyB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICBjdXRsYXNzOjpTdGF0dXMgX3N0YXR1cyA9IChzdGF0dXMpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgICAgaWYgKF9zdGF0dXMgIT0gY3V0bGFzczo6U3RhdHVzOjprU3VjY2VzcykgeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICAgICAgdGhyb3cgc3RkOjpydW50aW1lX2Vycm9yKCJDVVRMQVNTIGVycm9yIik7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgIH0gd2hpbGUgKDApCgogICAgI2RlZmluZSBDVURBX0NIRUNLKGV4cHIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICBkbyB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICBjdWRhRXJyb3JfdCBfZXJyID0gKGV4cHIpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgICAgaWYgKF9lcnIgIT0gY3VkYVN1Y2Nlc3MpIHsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXAogICAgICAgICAgdGhyb3cgc3RkOjpydW50aW1lX2Vycm9yKGN1ZGFHZXRFcnJvclN0cmluZyhfZXJyKSk7ICAgICAgICAgICAgICAgICAgICAgICAgIFwKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcCiAgICAgIH0gd2hpbGUgKDApCgogICAgdXNpbmcgUHJvYmxlbVNoYXBlID0gY3V0bGFzczo6Z2VtbTo6R3JvdXBQcm9ibGVtU2hhcGU8Y3V0ZTo6U2hhcGU8aW50LGludCxpbnQ+PjsKICAgIHVzaW5nIEVsZW1lbnRJbnB1dCA9IGN1dGxhc3M6OmZsb2F0X2UybTFfdDsKICAgIHVzaW5nIEVsZW1lbnRTRiAgICA9IGN1dGxhc3M6OmZsb2F0X3VlNG0zX3Q7CiAgICB1c2luZyBFbGVtZW50QyAgICAgPSBjdXRsYXNzOjpoYWxmX3Q7CgogICAgdXNpbmcgRWxlbWVudEEgPSBjdXRsYXNzOjpudl9mbG9hdDRfdDxFbGVtZW50SW5wdXQ+OwogICAgdXNpbmcgTGF5b3V0QSAgPSBjdXRsYXNzOjpsYXlvdXQ6OlJvd01ham9yOwogICAgY29uc3RleHByIGludCBBbGlnbm1lbnRBICA9IDMyOwoKICAgIHVzaW5nIEVsZW1lbnRCID0gY3V0bGFzczo6bnZfZmxvYXQ0X3Q8RWxlbWVudElucHV0PjsKICAgIHVzaW5nIExheW91dEIgID0gY3V0bGFzczo6bGF5b3V0OjpDb2x1bW5NYWpvcjsKICAgIGNvbnN0ZXhwciBpbnQgQWxpZ25tZW50QiAgPSAzMjsKCiAgICB1c2luZyBFbGVtZW50RCA9IEVsZW1lbnRDOwogICAgdXNpbmcgTGF5b3V0QyAgPSBjdXRsYXNzOjpsYXlvdXQ6OlJvd01ham9yOwogICAgY29uc3RleHByIGludCBBbGlnbm1lbnRDICA9IDEyOCAvIGN1dGxhc3M6OnNpemVvZl9iaXRzPEVsZW1lbnRDPjo6dmFsdWU7CiAgICBjb25zdGV4cHIgaW50IEFsaWdubWVudEQgID0gMTI4IC8gY3V0bGFzczo6c2l6ZW9mX2JpdHM8RWxlbWVudEQ+Ojp2YWx1ZTsKICAgIHVzaW5nIEVsZW1lbnRBY2N1bXVsYXRvciAgPSBmbG9hdDsKCiAgICB1c2luZyBBcmNoVGFnID0gY3V0bGFzczo6YXJjaDo6U20xMDA7CiAgICB1c2luZyBPcGVyYXRvckNsYXNzID0gY3V0bGFzczo6YXJjaDo6T3BDbGFzc0Jsb2NrU2NhbGVkVGVuc29yT3A7CiAgICB1c2luZyBTdGFnZUNvdW50VHlwZSA9IGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OlN0YWdlQ291bnRBdXRvOwogICAgdXNpbmcgQ2x1c3RlclNoYXBlID0gY3V0ZTo6U2hhcGU8aW50MzJfdCxpbnQzMl90LGN1dGU6Ol8xPjsKCiAgICBzdHJ1Y3QgTU1BMVNNQ29uZmlnIHsKICAgICAgdXNpbmcgTW1hVGlsZVNoYXBlICAgICA9IGN1dGU6OlNoYXBlPGN1dGU6Ol8xMjgsY3V0ZTo6XzI1NixjdXRlOjpfMjU2PjsKICAgICAgdXNpbmcgS2VybmVsU2NoZWR1bGUgICA9IGN1dGxhc3M6OmdlbW06Oktlcm5lbFB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMVNtTnZmNFNtMTAwOwogICAgICB1c2luZyBFcGlsb2d1ZVNjaGVkdWxlID0gY3V0bGFzczo6ZXBpbG9ndWU6OlB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMVNtOwogICAgfTsKCiAgICBzdHJ1Y3QgTU1BMlNNQ29uZmlnIHsKICAgICAgdXNpbmcgTW1hVGlsZVNoYXBlICAgICA9IGN1dGU6OlNoYXBlPGN1dGU6Ol8yNTYsY3V0ZTo6XzI1NixjdXRlOjpfMjU2PjsKICAgICAgdXNpbmcgS2VybmVsU2NoZWR1bGUgICA9IGN1dGxhc3M6OmdlbW06Oktlcm5lbFB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMlNtTnZmNFNtMTAwOwogICAgICB1c2luZyBFcGlsb2d1ZVNjaGVkdWxlID0gY3V0bGFzczo6ZXBpbG9ndWU6OlB0ckFycmF5VG1hV2FycFNwZWNpYWxpemVkMlNtOwogICAgfTsKCiAgICB1c2luZyBDb2xsZWN0aXZlRXBpbG9ndWUxU00gPSB0eXBlbmFtZSBjdXRsYXNzOjplcGlsb2d1ZTo6Y29sbGVjdGl2ZTo6Q29sbGVjdGl2ZUJ1aWxkZXI8CiAgICAgICAgQXJjaFRhZywgT3BlcmF0b3JDbGFzcywKICAgICAgICB0eXBlbmFtZSBNTUExU01Db25maWc6Ok1tYVRpbGVTaGFwZSwgQ2x1c3RlclNoYXBlLAogICAgICAgIGN1dGU6OlNoYXBlPGN1dGU6Ol8xMjgsY3V0ZTo6XzY0PiwKICAgICAgICBFbGVtZW50QWNjdW11bGF0b3IsIEVsZW1lbnRBY2N1bXVsYXRvciwKICAgICAgICBFbGVtZW50QywgTGF5b3V0QyAqLCBBbGlnbm1lbnRDLAogICAgICAgIEVsZW1lbnRELCBMYXlvdXRDICosIEFsaWdubWVudEQsCiAgICAgICAgdHlwZW5hbWUgTU1BMVNNQ29uZmlnOjpFcGlsb2d1ZVNjaGVkdWxlCiAgICA+OjpDb2xsZWN0aXZlT3A7CgogICAgdXNpbmcgQ29sbGVjdGl2ZU1haW5sb29wMVNNID0gdHlwZW5hbWUgY3V0bGFzczo6Z2VtbTo6Y29sbGVjdGl2ZTo6Q29sbGVjdGl2ZUJ1aWxkZXI8CiAgICAgIEFyY2hUYWcsIE9wZXJhdG9yQ2xhc3MsCiAgICAgIEVsZW1lbnRBLCBMYXlvdXRBICosIEFsaWdubWVudEEsCiAgICAgIEVsZW1lbnRCLCBMYXlvdXRCICosIEFsaWdubWVudEIsCiAgICAgIEVsZW1lbnRBY2N1bXVsYXRvciwKICAgICAgdHlwZW5hbWUgTU1BMVNNQ29uZmlnOjpNbWFUaWxlU2hhcGUsIENsdXN0ZXJTaGFwZSwKICAgICAgY3V0bGFzczo6Z2VtbTo6Y29sbGVjdGl2ZTo6U3RhZ2VDb3VudEF1dG9DYXJ2ZW91dDwKICAgICAgICBzdGF0aWNfY2FzdDxpbnQ+KHNpemVvZih0eXBlbmFtZSBDb2xsZWN0aXZlRXBpbG9ndWUxU006OlNoYXJlZFN0b3JhZ2UpKT4sCiAgICAgIHR5cGVuYW1lIE1NQTFTTUNvbmZpZzo6S2VybmVsU2NoZWR1bGUKICAgID46OkNvbGxlY3RpdmVPcDsKCiAgICB1c2luZyBHZW1tS2VybmVsMVNNID0gY3V0bGFzczo6Z2VtbTo6a2VybmVsOjpHZW1tVW5pdmVyc2FsPAogICAgICAgIFByb2JsZW1TaGFwZSwKICAgICAgICBDb2xsZWN0aXZlTWFpbmxvb3AxU00sCiAgICAgICAgQ29sbGVjdGl2ZUVwaWxvZ3VlMVNNCiAgICA+OwogICAgdXNpbmcgR2VtbTFTTSA9IGN1dGxhc3M6OmdlbW06OmRldmljZTo6R2VtbVVuaXZlcnNhbEFkYXB0ZXI8R2VtbUtlcm5lbDFTTT47CgogICAgdXNpbmcgQ29sbGVjdGl2ZUVwaWxvZ3VlMlNNID0gdHlwZW5hbWUgY3V0bGFzczo6ZXBpbG9ndWU6OmNvbGxlY3RpdmU6OkNvbGxlY3RpdmVCdWlsZGVyPAogICAgICAgIEFyY2hUYWcsIE9wZXJhdG9yQ2xhc3MsCiAgICAgICAgdHlwZW5hbWUgTU1BMlNNQ29uZmlnOjpNbWFUaWxlU2hhcGUsIENsdXN0ZXJTaGFwZSwKICAgICAgICBjdXRlOjpTaGFwZTxjdXRlOjpfMTI4LGN1dGU6Ol82ND4sCiAgICAgICAgRWxlbWVudEFjY3VtdWxhdG9yLCBFbGVtZW50QWNjdW11bGF0b3IsCiAgICAgICAgRWxlbWVudEMsIExheW91dEMgKiwgQWxpZ25tZW50QywKICAgICAgICBFbGVtZW50RCwgTGF5b3V0QyAqLCBBbGlnbm1lbnRELAogICAgICAgIHR5cGVuYW1lIE1NQTJTTUNvbmZpZzo6RXBpbG9ndWVTY2hlZHVsZQogICAgPjo6Q29sbGVjdGl2ZU9wOwoKICAgIHVzaW5nIENvbGxlY3RpdmVNYWlubG9vcDJTTSA9IHR5cGVuYW1lIGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OkNvbGxlY3RpdmVCdWlsZGVyPAogICAgICBBcmNoVGFnLCBPcGVyYXRvckNsYXNzLAogICAgICBFbGVtZW50QSwgTGF5b3V0QSAqLCBBbGlnbm1lbnRBLAogICAgICBFbGVtZW50QiwgTGF5b3V0QiAqLCBBbGlnbm1lbnRCLAogICAgICBFbGVtZW50QWNjdW11bGF0b3IsCiAgICAgIHR5cGVuYW1lIE1NQTJTTUNvbmZpZzo6TW1hVGlsZVNoYXBlLCBDbHVzdGVyU2hhcGUsCiAgICAgIGN1dGxhc3M6OmdlbW06OmNvbGxlY3RpdmU6OlN0YWdlQ291bnRBdXRvQ2FydmVvdXQ8CiAgICAgICAgc3RhdGljX2Nhc3Q8aW50PihzaXplb2YodHlwZW5hbWUgQ29sbGVjdGl2ZUVwaWxvZ3VlMlNNOjpTaGFyZWRTdG9yYWdlKSk+LAogICAgICB0eXBlbmFtZSBNTUEyU01Db25maWc6Oktlcm5lbFNjaGVkdWxlCiAgICA+OjpDb2xsZWN0aXZlT3A7CgogICAgdXNpbmcgR2VtbUtlcm5lbDJTTSA9IGN1dGxhc3M6OmdlbW06Omtlcm5lbDo6R2VtbVVuaXZlcnNhbDwKICAgICAgICBQcm9ibGVtU2hhcGUsCiAgICAgICAgQ29sbGVjdGl2ZU1haW5sb29wMlNNLAogICAgICAgIENvbGxlY3RpdmVFcGlsb2d1ZTJTTQogICAgPjsKICAgIHVzaW5nIEdlbW0yU00gPSBjdXRsYXNzOjpnZW1tOjpkZXZpY2U6OkdlbW1Vbml2ZXJzYWxBZGFwdGVyPEdlbW1LZXJuZWwyU00+OwoKICAgIHRlbXBsYXRlIDx0eXBlbmFtZSBUPgogICAgc3RydWN0IFBpbm5lZEhvc3RCdWZmZXIgewogICAgICBUKiBwdHIgPSBudWxscHRyOwogICAgICBzaXplX3QgY291bnQgPSAwOwoKICAgICAgUGlubmVkSG9zdEJ1ZmZlcigpID0gZGVmYXVsdDsKICAgICAgZXhwbGljaXQgUGlubmVkSG9zdEJ1ZmZlcihzaXplX3QgbikgeyBhbGxvY2F0ZShuKTsgfQoKICAgICAgdm9pZCBhbGxvY2F0ZShzaXplX3QgbikgewogICAgICAgIGlmIChwdHIgJiYgY291bnQgPj0gbikgewogICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICByZWxlYXNlKCk7CiAgICAgICAgY291bnQgPSBuOwogICAgICAgIENVREFfQ0hFQ0soY3VkYUhvc3RBbGxvYyhyZWludGVycHJldF9jYXN0PHZvaWQqKj4oJnB0ciksIG4gKiBzaXplb2YoVCksIGN1ZGFIb3N0QWxsb2NQb3J0YWJsZSkpOwogICAgICB9CgogICAgICB2b2lkIHJlbGVhc2UoKSB7CiAgICAgICAgaWYgKHB0cikgewogICAgICAgICAgY3VkYUZyZWVIb3N0KHB0cik7CiAgICAgICAgICBwdHIgPSBudWxscHRyOwogICAgICAgICAgY291bnQgPSAwOwogICAgICAgIH0KICAgICAgfQoKICAgICAgflBpbm5lZEhvc3RCdWZmZXIoKSB7IHJlbGVhc2UoKTsgfQoKICAgICAgVCogZGF0YSgpIGNvbnN0IHsgcmV0dXJuIHB0cjsgfQogICAgICBUJiBvcGVyYXRvcltdKHNpemVfdCBpZHgpIHsgcmV0dXJuIHB0cltpZHhdOyB9CiAgICB9OwoKICAgIHRlbXBsYXRlIDx0eXBlbmFtZSBUPgogICAgc3RydWN0IERldmljZUJ1ZmZlciB7CiAgICAgIFQqIHB0ciA9IG51bGxwdHI7CiAgICAgIHNpemVfdCBjb3VudCA9IDA7CgogICAgICBEZXZpY2VCdWZmZXIoKSA9IGRlZmF1bHQ7CiAgICAgIGV4cGxpY2l0IERldmljZUJ1ZmZlcihzaXplX3QgbikgeyBhbGxvY2F0ZShuKTsgfQoKICAgICAgdm9pZCBhbGxvY2F0ZShzaXplX3QgbikgewogICAgICAgIGlmIChwdHIgJiYgY291bnQgPj0gbikgewogICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICByZWxlYXNlKCk7CiAgICAgICAgY291bnQgPSBuOwogICAgICAgIENVREFfQ0hFQ0soY3VkYU1hbGxvYyhyZWludGVycHJldF9jYXN0PHZvaWQqKj4oJnB0ciksIG4gKiBzaXplb2YoVCkpKTsKICAgICAgfQoKICAgICAgdm9pZCByZWxlYXNlKCkgewogICAgICAgIGlmIChwdHIpIHsKICAgICAgICAgIGN1ZGFGcmVlKHB0cik7CiAgICAgICAgICBwdHIgPSBudWxscHRyOwogICAgICAgICAgY291bnQgPSAwOwogICAgICAgIH0KICAgICAgfQoKICAgICAgfkRldmljZUJ1ZmZlcigpIHsgcmVsZWFzZSgpOyB9CgogICAgICBUKiBnZXQoKSBjb25zdCB7IHJldHVybiBwdHI7IH0KCiAgICAgIHZvaWQgY29weV9mcm9tX2hvc3QoY29uc3QgVCogaG9zdCwgc2l6ZV90IG4sIGN1ZGFTdHJlYW1fdCBzdHJlYW0pIHsKICAgICAgICBDVURBX0NIRUNLKGN1ZGFNZW1jcHlBc3luYyhwdHIsIGhvc3QsIG4gKiBzaXplb2YoVCksIGN1ZGFNZW1jcHlIb3N0VG9EZXZpY2UsIHN0cmVhbSkpOwogICAgICB9CiAgICB9OwoKICAgIHRlbXBsYXRlIDx0eXBlbmFtZSBHZW1tLCBpbnQgQ2x1c3Rlck0sIGludCBDbHVzdGVyTj4KICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IHJ1bl9ncm91cGVkX2ltcGwoCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKSB7CgogICAgICBUT1JDSF9DSEVDSyhBLnNpemUoKSA9PSBCLnNpemUoKSwgIkEvQiBzaXplIG1pc21hdGNoIik7CiAgICAgIFRPUkNIX0NIRUNLKEEuc2l6ZSgpID09IEMuc2l6ZSgpLCAiQS9DIHNpemUgbWlzbWF0Y2giKTsKICAgICAgVE9SQ0hfQ0hFQ0soQS5zaXplKCkgPT0gU0ZBLnNpemUoKSwgIkEvU0ZBIHNpemUgbWlzbWF0Y2giKTsKICAgICAgVE9SQ0hfQ0hFQ0soQS5zaXplKCkgPT0gU0ZCLnNpemUoKSwgIkEvU0ZCIHNpemUgbWlzbWF0Y2giKTsKICAgICAgY29uc3QgaW50MzJfdCBncm91cHMgPSBzdGF0aWNfY2FzdDxpbnQzMl90PihBLnNpemUoKSk7CiAgICAgIFRPUkNIX0NIRUNLKGdyb3VwcyA+IDAsICJObyBncm91cHMiKTsKCiAgICAgIGludCBkZXZpY2VfaWQgPSBBWzBdLmdldF9kZXZpY2UoKTsKICAgICAgYzEwOjpjdWRhOjpDVURBR3VhcmQgZGV2aWNlX2d1YXJkKGRldmljZV9pZCk7CgogICAgICB1c2luZyBTdHJpZGVBID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVBOwogICAgICB1c2luZyBTdHJpZGVCID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVCOwogICAgICB1c2luZyBTdHJpZGVDID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVDOwogICAgICB1c2luZyBTdHJpZGVEID0gdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6SW50ZXJuYWxTdHJpZGVEOwogICAgICB1c2luZyBMYXlvdXRTRkEgPSB0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpDb2xsZWN0aXZlTWFpbmxvb3A6OkludGVybmFsTGF5b3V0U0ZBOwogICAgICB1c2luZyBMYXlvdXRTRkIgPSB0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpDb2xsZWN0aXZlTWFpbmxvb3A6OkludGVybmFsTGF5b3V0U0ZCOwogICAgICB1c2luZyBTbTF4eEJsa1NjYWxlZENvbmZpZyA9IHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkNvbGxlY3RpdmVNYWlubG9vcDo6U20xeHhCbGtTY2FsZWRDb25maWc7CgogICAgICBzdGF0aWMgc2l6ZV90IGNhcGFjaXR5ID0gMDsKICAgICAgc3RhdGljIGludCBjYWNoZWRfZGV2aWNlID0gLTE7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRBIGNvbnN0Kj4gcHRyX0FfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEIgY29uc3QqPiBwdHJfQl9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QyBjb25zdCo+IHB0cl9DX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkVwaWxvZ3VlT3V0cHV0T3A6OkVsZW1lbnRPdXRwdXQqPiBwdHJfRF9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPiBwdHJfU0ZBX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPHR5cGVuYW1lIEdlbW06OkdlbW1LZXJuZWw6OkVsZW1lbnRTRiBjb25zdCo+IHB0cl9TRkJfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlQT4gc3RyaWRlX0FfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlQj4gc3RyaWRlX0JfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlQz4gc3RyaWRlX0NfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8U3RyaWRlRD4gc3RyaWRlX0RfaG9zdDsKICAgICAgc3RhdGljIFBpbm5lZEhvc3RCdWZmZXI8TGF5b3V0U0ZBPiBsYXlvdXRfU0ZBX2hvc3Q7CiAgICAgIHN0YXRpYyBQaW5uZWRIb3N0QnVmZmVyPExheW91dFNGQj4gbGF5b3V0X1NGQl9ob3N0OwogICAgICBzdGF0aWMgUGlubmVkSG9zdEJ1ZmZlcjx0eXBlbmFtZSBQcm9ibGVtU2hhcGU6OlVuZGVybHlpbmdQcm9ibGVtU2hhcGU+IHByb2JsZW1fc2l6ZXNfaG9zdDsKCiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgUHJvYmxlbVNoYXBlOjpVbmRlcmx5aW5nUHJvYmxlbVNoYXBlPiBwcm9ibGVtX3NpemVzX2RldmljZTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QSBjb25zdCo+IHB0cl9BOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRCIGNvbnN0Kj4gcHRyX0I7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6RWxlbWVudFNGIGNvbnN0Kj4gcHRyX1NGQTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPiBwdHJfU0ZCOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPHR5cGVuYW1lIEdlbW06OkVsZW1lbnRDIGNvbnN0Kj4gcHRyX0M7CiAgICAgIHN0YXRpYyBEZXZpY2VCdWZmZXI8dHlwZW5hbWUgR2VtbTo6RXBpbG9ndWVPdXRwdXRPcDo6RWxlbWVudE91dHB1dCo+IHB0cl9EOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUE+IHN0cmlkZV9BOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUI+IHN0cmlkZV9COwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUM+IHN0cmlkZV9DOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPFN0cmlkZUQ+IHN0cmlkZV9EOwogICAgICBzdGF0aWMgRGV2aWNlQnVmZmVyPExheW91dFNGQT4gbGF5b3V0X1NGQTsKICAgICAgc3RhdGljIERldmljZUJ1ZmZlcjxMYXlvdXRTRkI+IGxheW91dF9TRkI7CiAgICAgIHN0YXRpYyBzaXplX3Qgd29ya3NwYWNlX3NpemVfY2FjaGVkID0gMDsKICAgICAgc3RhdGljIHRvcmNoOjpUZW5zb3Igd29ya3NwYWNlX3RlbnNvcjsKCiAgICAgIGlmIChjYWNoZWRfZGV2aWNlICE9IGRldmljZV9pZCB8fCBjYXBhY2l0eSA8IHN0YXRpY19jYXN0PHNpemVfdD4oZ3JvdXBzKSkgewogICAgICAgIGNhY2hlZF9kZXZpY2UgPSBkZXZpY2VfaWQ7CiAgICAgICAgY2FwYWNpdHkgPSBzdGF0aWNfY2FzdDxzaXplX3Q+KGdyb3Vwcyk7CiAgICAgICAgcHRyX0FfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9CX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQ19ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX0RfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkFfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkJfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9BX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfQl9ob3N0LmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0NfaG9zdC5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9EX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBsYXlvdXRfU0ZBX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBsYXlvdXRfU0ZCX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwcm9ibGVtX3NpemVzX2hvc3QuYWxsb2NhdGUoZ3JvdXBzKTsKCiAgICAgICAgcHJvYmxlbV9zaXplc19kZXZpY2UuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQS5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9CLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgcHRyX1NGQS5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9TRkIuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBwdHJfQy5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHB0cl9ELmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0EuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBzdHJpZGVfQi5hbGxvY2F0ZShncm91cHMpOwogICAgICAgIHN0cmlkZV9DLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgc3RyaWRlX0QuYWxsb2NhdGUoZ3JvdXBzKTsKICAgICAgICBsYXlvdXRfU0ZBLmFsbG9jYXRlKGdyb3Vwcyk7CiAgICAgICAgbGF5b3V0X1NGQi5hbGxvY2F0ZShncm91cHMpOwogICAgICB9CgogICAgICBmb3IgKGludDMyX3QgaSA9IDA7IGkgPCBncm91cHM7ICsraSkgewogICAgICAgIFRPUkNIX0NIRUNLKEFbaV0uaXNfY3VkYSgpLCAiQSBtdXN0IGJlIENVREEiKTsKICAgICAgICBUT1JDSF9DSEVDSyhCW2ldLmlzX2N1ZGEoKSwgIkIgbXVzdCBiZSBDVURBIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soQ1tpXS5pc19jdWRhKCksICJDIG11c3QgYmUgQ1VEQSIpOwogICAgICAgIFRPUkNIX0NIRUNLKFNGQVtpXS5pc19jdWRhKCksICJTRkEgbXVzdCBiZSBDVURBIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soU0ZCW2ldLmlzX2N1ZGEoKSwgIlNGQiBtdXN0IGJlIENVREEiKTsKCiAgICAgICAgaW50MzJfdCBtID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQVtpXS5zaXplKDApKTsKICAgICAgICBpbnQzMl90IGtfcGFja2VkID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQVtpXS5zaXplKDEpKTsKICAgICAgICBpbnQzMl90IGsgPSBrX3BhY2tlZCAqIDI7CiAgICAgICAgaW50MzJfdCBuID0gc3RhdGljX2Nhc3Q8aW50MzJfdD4oQltpXS5zaXplKDApKTsKICAgICAgICBUT1JDSF9DSEVDSyhCW2ldLnNpemUoMSkgKiAyID09IGssICJLIG1pc21hdGNoIik7CiAgICAgICAgVE9SQ0hfQ0hFQ0soQ1tpXS5zaXplKDApID09IG0gJiYgQ1tpXS5zaXplKDEpID09IG4sICJDIHNoYXBlIG1pc21hdGNoIik7CgogICAgICAgIHByb2JsZW1fc2l6ZXNfaG9zdFtpXSA9IGN1dGU6Om1ha2Vfc2hhcGUobSwgbiwgayk7CiAgICAgICAgc3RyaWRlX0FfaG9zdFtpXSA9IGN1dGxhc3M6Om1ha2VfY3V0ZV9wYWNrZWRfc3RyaWRlKFN0cmlkZUF7fSwge20sIGssIDF9KTsKICAgICAgICBzdHJpZGVfQl9ob3N0W2ldID0gY3V0bGFzczo6bWFrZV9jdXRlX3BhY2tlZF9zdHJpZGUoU3RyaWRlQnt9LCB7biwgaywgMX0pOwogICAgICAgIHN0cmlkZV9DX2hvc3RbaV0gPSBjdXRsYXNzOjptYWtlX2N1dGVfcGFja2VkX3N0cmlkZShTdHJpZGVDe30sIHttLCBuLCAxfSk7CiAgICAgICAgc3RyaWRlX0RfaG9zdFtpXSA9IGN1dGxhc3M6Om1ha2VfY3V0ZV9wYWNrZWRfc3RyaWRlKFN0cmlkZUR7fSwge20sIG4sIDF9KTsKICAgICAgICBsYXlvdXRfU0ZBX2hvc3RbaV0gPSBTbTF4eEJsa1NjYWxlZENvbmZpZzo6dGlsZV9hdG9tX3RvX3NoYXBlX1NGQShjdXRlOjptYWtlX3NoYXBlKG0sIG4sIGssIDEpKTsKICAgICAgICBsYXlvdXRfU0ZCX2hvc3RbaV0gPSBTbTF4eEJsa1NjYWxlZENvbmZpZzo6dGlsZV9hdG9tX3RvX3NoYXBlX1NGQihjdXRlOjptYWtlX3NoYXBlKG0sIG4sIGssIDEpKTsKCiAgICAgICAgcHRyX0FfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6RWxlbWVudEEgY29uc3QqPihBW2ldLmRhdGFfcHRyKCkpOwogICAgICAgIHB0cl9CX2hvc3RbaV0gPSByZWludGVycHJldF9jYXN0PHR5cGVuYW1lIEdlbW06OkVsZW1lbnRCIGNvbnN0Kj4oQltpXS5kYXRhX3B0cigpKTsKICAgICAgICBwdHJfQ19ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpFbGVtZW50QyBjb25zdCo+KENbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX0RfaG9zdFtpXSA9IHJlaW50ZXJwcmV0X2Nhc3Q8dHlwZW5hbWUgR2VtbTo6RXBpbG9ndWVPdXRwdXRPcDo6RWxlbWVudE91dHB1dCo+KENbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX1NGQV9ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPihTRkFbaV0uZGF0YV9wdHIoKSk7CiAgICAgICAgcHRyX1NGQl9ob3N0W2ldID0gcmVpbnRlcnByZXRfY2FzdDx0eXBlbmFtZSBHZW1tOjpHZW1tS2VybmVsOjpFbGVtZW50U0YgY29uc3QqPihTRkJbaV0uZGF0YV9wdHIoKSk7CiAgICAgIH0KCiAgICAgIGF1dG8gc3RyZWFtX29iaiA9IGMxMDo6Y3VkYTo6Z2V0Q3VycmVudENVREFTdHJlYW0oZGV2aWNlX2lkKTsKICAgICAgYzEwOjpjdWRhOjpDVURBU3RyZWFtR3VhcmQgc3RyZWFtX2d1YXJkKHN0cmVhbV9vYmopOwogICAgICBjdWRhU3RyZWFtX3Qgc3RyZWFtID0gc3RyZWFtX29iai5zdHJlYW0oKTsKICAgICAgcHJvYmxlbV9zaXplc19kZXZpY2UuY29weV9mcm9tX2hvc3QocHJvYmxlbV9zaXplc19ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfQS5jb3B5X2Zyb21faG9zdChwdHJfQV9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfQi5jb3B5X2Zyb21faG9zdChwdHJfQl9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBwdHJfU0ZBLmNvcHlfZnJvbV9ob3N0KHB0cl9TRkFfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKICAgICAgcHRyX1NGQi5jb3B5X2Zyb21faG9zdChwdHJfU0ZCX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHB0cl9DLmNvcHlfZnJvbV9ob3N0KHB0cl9DX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHB0cl9ELmNvcHlfZnJvbV9ob3N0KHB0cl9EX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9BLmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9BX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9CLmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9CX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9DLmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9DX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIHN0cmlkZV9ELmNvcHlfZnJvbV9ob3N0KHN0cmlkZV9EX2hvc3QuZGF0YSgpLCBncm91cHMsIHN0cmVhbSk7CiAgICAgIGxheW91dF9TRkEuY29weV9mcm9tX2hvc3QobGF5b3V0X1NGQV9ob3N0LmRhdGEoKSwgZ3JvdXBzLCBzdHJlYW0pOwogICAgICBsYXlvdXRfU0ZCLmNvcHlfZnJvbV9ob3N0KGxheW91dF9TRkJfaG9zdC5kYXRhKCksIGdyb3Vwcywgc3RyZWFtKTsKCiAgICAgIGN1dGxhc3M6Oktlcm5lbEhhcmR3YXJlSW5mbyBod19pbmZvOwogICAgICBod19pbmZvLmRldmljZV9pZCA9IGRldmljZV9pZDsKICAgICAgaHdfaW5mby5zbV9jb3VudCA9IGN1dGxhc3M6Oktlcm5lbEhhcmR3YXJlSW5mbzo6cXVlcnlfZGV2aWNlX211bHRpcHJvY2Vzc29yX2NvdW50KGh3X2luZm8uZGV2aWNlX2lkKTsKICAgICAgaHdfaW5mby5jbHVzdGVyX3NoYXBlID0gZGltMyhDbHVzdGVyTSwgQ2x1c3Rlck4sIDEpOwogICAgICBod19pbmZvLmNsdXN0ZXJfc2hhcGVfZmFsbGJhY2sgPSBkaW0zKENsdXN0ZXJNLCBDbHVzdGVyTiwgMSk7CgogICAgICB0eXBlbmFtZSBHZW1tOjpBcmd1bWVudHMgYXJndW1lbnRzOwogICAgICBkZWNsdHlwZShhcmd1bWVudHMuZXBpbG9ndWUudGhyZWFkKSBmdXNpb25fYXJnczsKICAgICAgZnVzaW9uX2FyZ3MuYWxwaGFfcHRyID0gbnVsbHB0cjsKICAgICAgZnVzaW9uX2FyZ3MuYmV0YV9wdHIgPSBudWxscHRyOwogICAgICBmdXNpb25fYXJncy5hbHBoYSA9IEVsZW1lbnRBY2N1bXVsYXRvcigxKTsKICAgICAgZnVzaW9uX2FyZ3MuYmV0YSA9IEVsZW1lbnRBY2N1bXVsYXRvcigwKTsKICAgICAgZnVzaW9uX2FyZ3MuYWxwaGFfcHRyX2FycmF5ID0gbnVsbHB0cjsKICAgICAgZnVzaW9uX2FyZ3MuYmV0YV9wdHJfYXJyYXkgPSBudWxscHRyOwogICAgICBmdXNpb25fYXJncy5kQWxwaGEgPSB7Y3V0ZTo6XzB7fSwgY3V0ZTo6XzB7fSwgMH07CiAgICAgIGZ1c2lvbl9hcmdzLmRCZXRhID0ge2N1dGU6Ol8we30sIGN1dGU6Ol8we30sIDB9OwoKICAgICAgdHlwZW5hbWUgR2VtbTo6R2VtbUtlcm5lbDo6VGlsZVNjaGVkdWxlckFyZ3VtZW50cyBzY2hlZHVsZXI7CiAgICAgIHNjaGVkdWxlci5yYXN0ZXJfb3JkZXIgPSBjdXRsYXNzOjpnZW1tOjprZXJuZWw6OmRldGFpbDo6UmFzdGVyT3JkZXJPcHRpb25zOjpBbG9uZ047CgogICAgICBhcmd1bWVudHMgPSB0eXBlbmFtZSBHZW1tOjpBcmd1bWVudHMgewogICAgICAgIGN1dGxhc3M6OmdlbW06OkdlbW1Vbml2ZXJzYWxNb2RlOjprR3JvdXBlZCwKICAgICAgICB7Z3JvdXBzLCBwcm9ibGVtX3NpemVzX2RldmljZS5nZXQoKSwgbnVsbHB0cn0sCiAgICAgICAge3B0cl9BLmdldCgpLCBzdHJpZGVfQS5nZXQoKSwgcHRyX0IuZ2V0KCksIHN0cmlkZV9CLmdldCgpLAogICAgICAgICBwdHJfU0ZBLmdldCgpLCBsYXlvdXRfU0ZBLmdldCgpLCBwdHJfU0ZCLmdldCgpLCBsYXlvdXRfU0ZCLmdldCgpfSwKICAgICAgICB7ZnVzaW9uX2FyZ3MsIHB0cl9DLmdldCgpLCBzdHJpZGVfQy5nZXQoKSwgcHRyX0QuZ2V0KCksIHN0cmlkZV9ELmdldCgpfSwKICAgICAgICBod19pbmZvLCBzY2hlZHVsZXIKICAgICAgfTsKCiAgICAgIEdlbW0gZ2VtbTsKICAgICAgc2l6ZV90IHdvcmtzcGFjZV9zaXplID0gR2VtbTo6Z2V0X3dvcmtzcGFjZV9zaXplKGFyZ3VtZW50cyk7CiAgICAgIGlmICghd29ya3NwYWNlX3RlbnNvci5kZWZpbmVkKCkgfHwgY2FjaGVkX2RldmljZSAhPSBkZXZpY2VfaWQgfHwgd29ya3NwYWNlX3NpemVfY2FjaGVkIDwgd29ya3NwYWNlX3NpemUpIHsKICAgICAgICBhdXRvIG9wdHMgPSB0b3JjaDo6VGVuc29yT3B0aW9ucygpLmRldmljZSh0b3JjaDo6a0NVREEsIGRldmljZV9pZCkuZHR5cGUodG9yY2g6OmtVSW50OCk7CiAgICAgICAgd29ya3NwYWNlX3RlbnNvciA9IHRvcmNoOjplbXB0eSh7c3RhdGljX2Nhc3Q8bG9uZz4od29ya3NwYWNlX3NpemUpfSwgb3B0cyk7CiAgICAgICAgd29ya3NwYWNlX3NpemVfY2FjaGVkID0gd29ya3NwYWNlX3NpemU7CiAgICAgIH0KICAgICAgdm9pZCogd29ya3NwYWNlX3B0ciA9IHdvcmtzcGFjZV9zaXplID8gd29ya3NwYWNlX3RlbnNvci5kYXRhX3B0cigpIDogbnVsbHB0cjsKCiAgICAgIENVVExBU1NfQ0hFQ0soZ2VtbS5jYW5faW1wbGVtZW50KGFyZ3VtZW50cykpOwogICAgICBDVVRMQVNTX0NIRUNLKGdlbW0uaW5pdGlhbGl6ZShhcmd1bWVudHMsIHdvcmtzcGFjZV9wdHIsIHN0cmVhbSkpOwogICAgICBDVVRMQVNTX0NIRUNLKGdlbW0ucnVuKHN0cmVhbSkpOwoKICAgICAgcmV0dXJuIEM7CiAgICB9CgogICAgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4gZ3JvdXBlZF9nZW1tXzFzbSgKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgQiwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgQywKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZBLAogICAgICAgIGNvbnN0IHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+JiBTRkIpIHsKICAgICAgcmV0dXJuIHJ1bl9ncm91cGVkX2ltcGw8R2VtbTFTTSwgMiwgMT4oQSwgQiwgQywgU0ZBLCBTRkIpOwogICAgfQoKICAgIHN0ZDo6dmVjdG9yPHRvcmNoOjpUZW5zb3I+IGdyb3VwZWRfZ2VtbV8yc20oCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEEsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEIsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIEMsCiAgICAgICAgY29uc3Qgc3RkOjp2ZWN0b3I8dG9yY2g6OlRlbnNvcj4mIFNGQSwKICAgICAgICBjb25zdCBzdGQ6OnZlY3Rvcjx0b3JjaDo6VGVuc29yPiYgU0ZCKSB7CiAgICAgIHJldHVybiBydW5fZ3JvdXBlZF9pbXBsPEdlbW0yU00sIDIsIDE+KEEsIEIsIEMsIFNGQSwgU0ZCKTsKICAgIH0KICAgIAo="_ext_mod = None_ext_fail = False⋯ 181 unchanged lines_scaled_mm_out_supported: bool | None = None_scaled_mm_fast_accum_supported: bool | None = None_grouped_mm_v2_supported: bool | None = None- _grouped_mm_supported: bool | None = None+ _grouped_mm_v2_fn = None+ _grouped_mm_supported: bool | None = False_group_cache: dict[tuple, dict] = {}_group_cache_padded: dict[tuple, dict] = {}_grouped_strategy_cache: dict[tuple, str] = {}⋯ 6 unchanged lines_swizzle_cached = None_global_swizzle_cached = None_global_scale_cache: dict[tuple[int, int, int], torch.Tensor] = {}- _dbg_on = False+ _dbg_on = bool(os.environ.get("NVFP4_DEBUG"))_dbg_once = Falsedef _dbg(msg: str) -> None:- global _dbg_once- if _dbg_on and not _dbg_once:+ if _dbg_on:print(msg)- _dbg_once = Truedef blocked_from_reordered(sf_reordered: torch.Tensor, l_idx: int) -> torch.Tensor:⋯ 12 unchanged linesdef _get_grouped_mm_v2():- global _grouped_mm_v2_supported+ global _grouped_mm_v2_supported, _grouped_mm_v2_fnif _grouped_mm_v2_supported is None:try:- _ = torch.nn.functional.scaled_grouped_mm+ _grouped_mm_v2_fn = torch.nn.functional.scaled_grouped_mm_grouped_mm_v2_supported = Trueexcept Exception:- _grouped_mm_v2_supported = False- return None+ try:+ _grouped_mm_v2_fn = torch.ops.aten._scaled_grouped_mm_v2+ _grouped_mm_v2_supported = True+ except Exception:+ _grouped_mm_v2_supported = False+ _grouped_mm_v2_fn = None+ return Noneif _grouped_mm_v2_supported:- return torch.nn.functional.scaled_grouped_mm+ return _grouped_mm_v2_fnreturn None⋯ 22 unchanged linesreturn None+ def _enum_to_int(val):+ try:+ return int(val)+ except Exception:+ return val++def _get_scale_swizzle():global _scale_recipe_cached, _swizzle_cachedglobal _global_scale_recipe_cached, _global_swizzle_cached⋯ 4 unchanged lines_swizzle_cached,_global_swizzle_cached,)- try:- scaling_type = torch.nn.functional.ScalingType- swizzle_type = torch.nn.functional.SwizzleType- except Exception:+ scaling_type = None+ swizzle_type = None+ for mod in (+ getattr(torch, "nn", None).functional if hasattr(torch, "nn") else None,+ torch,+ getattr(torch, "_C", None),+ ):+ if mod is None:+ continue+ if scaling_type is None:+ scaling_type = getattr(mod, "ScalingType", None) or getattr(mod, "_ScalingType", None)+ if swizzle_type is None:+ swizzle_type = getattr(mod, "SwizzleType", None) or getattr(mod, "_SwizzleType", None)+ if scaling_type is not None and swizzle_type is not None:+ break+ if scaling_type is None or swizzle_type is None:return None, None, None, None- block_scale = _find_enum_value(scaling_type, "BLOCKWISE_16")+ block_scale = _find_enum_value(scaling_type, "BLOCKWISE_1X16")+ if block_scale is None:+ block_scale = _find_enum_value(scaling_type, "BLOCKWISE1X16")+ if block_scale is None:+ block_scale = _find_enum_value(scaling_type, "BLOCKWISE_16")+ if block_scale is None:+ block_scale = _find_enum_value(scaling_type, "BLOCKWISE16")global_scale = _find_enum_value(scaling_type, "TENSORWISE")if global_scale is None:global_scale = _find_enum_value(scaling_type, "TENSOR_WISE")⋯ 45 unchanged linesif cached is not None:return cached+ k_val = problem_sizes[0][2] if problem_sizes else 0+ k_blocks = _round_up((k_val + 15) // 16, 4)+a_blocks = []b_blocks = []scale_a_blocks = []⋯ 23 unchanged linesb_blocks.append(b_ref[:, :, 0])- scale_a_blocks.append(blocked_from_reordered(sfa_reordered, 0))+ scale_a = blocked_from_reordered(sfa_reordered, 0)+ scale_expected = m_pad * k_blocks+ if scale_a.numel() < scale_expected:+ pad_len = scale_expected - scale_a.numel()+ scale_a = torch.cat(+ [+ scale_a,+ torch.zeros((pad_len,), device=scale_a.device, dtype=scale_a.dtype),+ ],+ dim=0,+ )+ scale_a_blocks.append(scale_a.view(m_pad, k_blocks))scale_b_blocks.append(blocked_from_reordered(sfb_reordered, 0))offset += m_padoffs.append(offset)m_pad_max = max(m_pads) if m_pads else 0- k_val = problem_sizes[0][2] if problem_sizes else 0- k_blocks = _round_up((k_val + 15) // 16, 4)scale_len_pad = m_pad_max * k_blocksa_concat = torch.cat(a_blocks, dim=0)⋯ 259 unchanged linesoffs_cpu,output_dtype,):- for offs_arg in (offs, offs_cpu):+ for offs_arg in (offs_cpu, offs):try:return func(a,⋯ 43 unchanged linesoffs_cpu,output_dtype,):- for offs_arg in (offs, offs_cpu):+ for offs_arg in (offs_cpu, offs):try:return func(a,⋯ 98 unchanged linesb_nk = grouped.get("b_nk")if b_nk is None:return None- b_kn = b_nk.transpose(1, 2).contiguous()+ if b_nk.dim() == 2:+ b_kn = b_nk.transpose(0, 1)+ else:+ b_kn = b_nk.transpose(1, 2)grouped["b_kn"] = b_knreturn b_kn⋯ 8 unchanged linesreturn Nonestack = []for scale_a in scale_a_blocks:+ scale_a = scale_a.reshape(-1)if scale_a.numel() < scale_len_pad:pad_len = scale_len_pad - scale_a.numel()scale_a = torch.cat(⋯ 31 unchanged linesext_res = _try_ext(abc_tensors, sfasfb_reordered_tensors, problem_sizes)if ext_res is not None:+ _dbg("path:ext")return ext_resif _force_ext_only:raise RuntimeError("ext path failed")if _batched_supported is not False:try:- batch_sets = _batch_partitions(problem_sizes, 1.6)+ batch_sets = _batch_partitions(problem_sizes, 4.0)if batch_sets:compiled_func = compile_kernel()batched_results: list[torch.Tensor | None] = [⋯ 58 unchanged linesbatched_results[orig_idx] = c_refif all(item is not None for item in batched_results):_batched_supported = True+ _dbg("path:batched")return batched_results # type: ignore[return-value]except Exception:_batched_supported = False⋯ 40 unchanged linescompiled_func(a_ptr, b_ptr, sfa_ptr, sfb_ptr, c_ptr, (m, n, k, l))result_tensors.append(c_ref)_cute_supported = True+ _dbg("path:cute")return result_tensorsexcept Exception:_cute_supported = False⋯ 39 unchanged lineselse:if global_scale is not None:attempts = [- "2d:concat:nk:group",+ "3d:stack:kn:scalar",+ "3d:stack:kn:group",+ "2d:concat:kn:scalar","2d:concat:kn:group",- "2d:stack:nk:group",+ "2d:stack:kn:scalar","2d:stack:kn:group",- "3d:stack:nk:group",- "3d:stack:kn:group",- "3d:stack:nk:scalar",- "3d:stack:kn:scalar",]else:attempts = [- "2d:concat:nk:none",+ "3d:stack:kn:none","2d:concat:kn:none",- "2d:stack:nk:none","2d:stack:kn:none",- "3d:stack:nk:none",- "3d:stack:kn:none",]for attempt in attempts:⋯ 45 unchanged linesglobal_b = _get_global_scale(len(abc_tensors), a.device, per_group)scale_a = [scale_a_block, global_a]scale_b = [scale_b_block, global_b]- scale_recipe_a = [block_scale, global_scale]- scale_recipe_b = [block_scale, global_scale]+ scale_recipe_a = [_enum_to_int(block_scale), _enum_to_int(global_scale)]+ scale_recipe_b = [_enum_to_int(block_scale), _enum_to_int(global_scale)]swizzle_a = [- block_swizzle,- global_swizzle if global_swizzle is not None else block_swizzle,+ _enum_to_int(block_swizzle),+ _enum_to_int(global_swizzle if global_swizzle is not None else block_swizzle),]swizzle_b = [- block_swizzle,- global_swizzle if global_swizzle is not None else block_swizzle,+ _enum_to_int(block_swizzle),+ _enum_to_int(global_swizzle if global_swizzle is not None else block_swizzle),]else:scale_a = scale_a_blockscale_b = scale_b_block- scale_recipe_a = block_scale- scale_recipe_b = block_scale- swizzle_a = block_swizzle- swizzle_b = block_swizzle+ scale_recipe_a = _enum_to_int(block_scale)+ scale_recipe_b = _enum_to_int(block_scale)+ swizzle_a = _enum_to_int(block_swizzle)+ swizzle_b = _enum_to_int(block_swizzle)out = _try_scaled_grouped_mm_v2(grouped_mm_v2,⋯ 29 unchanged linesif out is not None:_grouped_strategy_cache[strategy_key] = attemptresult_tensors = emit(out, grouped)+ _dbg("path:v2:" + attempt)return result_tensors_grouped_strategy_cache[strategy_key] = "skip_v2"⋯ 44 unchanged linesif out is not None:_grouped_strategy_cache_v1[strategy_key] = attemptresult_tensors = _emit_from_2d(out, grouped)+ _dbg("path:v1:" + attempt)return result_tensors_grouped_strategy_cache_v1[strategy_key] = "skip_v1"⋯ 16 unchanged linesc_ref[:, :, l_idx],)result_tensors.append(c_ref)+ _dbg("path:per_group")return result_tensors
scrolls · 307 diff lines total
Best evidence level for this revision: reported
JSON