Skip to content
KernelIndex
Search⌘K

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
NVFP4 group GEMMsuite of 4 cases
NVIDIA B200
57.2µs
#69 of 145
2026-01-28

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.

fp4tiled_mma, # Tiled MMA object defining NVFP4 GEMM compute pattern
mbarriertmem_alloc_barrier = pipeline.NamedBarrier(
shared-memorya_smem_layout_staged: cute.ComposedLayout,
tcgen05acc_tmem_ptr + tcgen05.find_tmem_tensor_col_offset(tCtAcc),
warp-specializationab_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 lines
pass
_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 = False
def _dbg(msg: str) -> None:
- global _dbg_once
- if _dbg_on and not _dbg_once:
+ if _dbg_on:
print(msg)
- _dbg_once = True
def blocked_from_reordered(sf_reordered: torch.Tensor, l_idx: int) -> torch.Tensor:
⋯ 12 unchanged lines
def _get_grouped_mm_v2():
- global _grouped_mm_v2_supported
+ global _grouped_mm_v2_supported, _grouped_mm_v2_fn
if _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 = True
except 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 None
if _grouped_mm_v2_supported:
- return torch.nn.functional.scaled_grouped_mm
+ return _grouped_mm_v2_fn
return None
⋯ 22 unchanged lines
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
⋯ 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 lines
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 = []
⋯ 23 unchanged lines
b_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_pad
offs.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_blocks
a_concat = torch.cat(a_blocks, dim=0)
⋯ 259 unchanged lines
offs_cpu,
output_dtype,
):
- for offs_arg in (offs, offs_cpu):
+ for offs_arg in (offs_cpu, offs):
try:
return func(
a,
⋯ 43 unchanged lines
offs_cpu,
output_dtype,
):
- for offs_arg in (offs, offs_cpu):
+ for offs_arg in (offs_cpu, offs):
try:
return func(
a,
⋯ 98 unchanged lines
b_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_kn
return b_kn
⋯ 8 unchanged lines
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(
⋯ 31 unchanged lines
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, 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 lines
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
⋯ 40 unchanged lines
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
⋯ 39 unchanged lines
else:
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 lines
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 = [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_block
scale_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 lines
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"
⋯ 44 unchanged lines
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"
⋯ 16 unchanged lines
c_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