CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1156 lines
39 KiB
C++
1156 lines
39 KiB
C++
// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* helper_rocke.helpers.distribution.c -- C99 port of make_static_tile_distribution
|
|
* (and the supporting TileDistributionEncoding constructor + validation) from
|
|
* rocke/helpers/distribution.py.
|
|
*
|
|
* make_static_tile_distribution emits NO IR (it is a pure host-side analysis
|
|
* pass), so there are zero rocke_b_* calls and the byte-identical-op-sequence
|
|
* requirement is the empty sequence. The fidelity that matters is the produced
|
|
* contributor table, which is reproduced bucket-for-bucket from the Python:
|
|
*
|
|
* contributors: List[List[Optional[_HBucketRef]]] = [
|
|
* [None for _ in hs] for hs in encoding.Hs
|
|
* ]
|
|
* # Ps cover their entries first; Ys fill any remaining holes.
|
|
* for pi, (maj_seq, min_seq) in enumerate(zip(Ps2RHs_major, Ps2RHs_minor)):
|
|
* for inner_idx, (maj, minor) in enumerate(zip(maj_seq, min_seq)):
|
|
* if maj == 0: continue # R contributors don't enter X
|
|
* contributors[maj-1][minor] = _HBucketRef("P", pi, inner_idx)
|
|
* for yi, (maj, minor) in enumerate(zip(Ys2RHs_major, Ys2RHs_minor)):
|
|
* if maj == 0: continue # R contributor; not in X
|
|
* contributors[maj-1][minor] = _HBucketRef("Y", yi)
|
|
* # defensive: every H bucket must be mapped.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "rocke/arena.h"
|
|
#include "rocke/error.hpp"
|
|
#include "rocke/helper_rocke.helpers.distribution.h"
|
|
#include "rocke/ir.h"
|
|
|
|
/* ----------------------------------------------------------------- helpers */
|
|
|
|
/* Set the builder's sticky error (first failure wins) and return NULL. Mirrors
|
|
* the private rocke_i_set_err but is reproduced here so this public helper module
|
|
* binds only to rocke/ir.h's public struct fields (status + err), not the private
|
|
* ir_internal.h. */
|
|
/* Raise the failure as a ckc::Error (mirroring the Python `raise`); the public
|
|
* entry boundary catches it and records status + message on the builder, so the
|
|
* C ABI is unchanged. [[noreturn]] keeps the existing `return (T*)rocke_dist_set_err(...)`
|
|
* call sites valid -- the cast/return is simply never reached. */
|
|
[[noreturn]] static void*
|
|
rocke_dist_set_err(rocke_ir_builder_t* b, rocke_status_t st, const char* fmt, ...)
|
|
{
|
|
(void)b;
|
|
char msg[ROCKE_ERR_MSG_CAP];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
(void)vsnprintf(msg, sizeof(msg), fmt, ap);
|
|
va_end(ap);
|
|
msg[sizeof(msg) - 1] = '\0';
|
|
ckc::raise_status(st, msg);
|
|
}
|
|
|
|
static bool rocke_dist_live(const rocke_ir_builder_t* b)
|
|
{
|
|
return b != NULL && b->status == ROCKE_OK;
|
|
}
|
|
|
|
/* Python frozen-dataclass _prod over a small int sequence. (Currently used only
|
|
* by the validation reproduction below; kept for parity with the Python
|
|
* module's _prod helper that the encoding properties share.) */
|
|
static int rocke_dist_prod(const int* xs, int n)
|
|
{
|
|
int p = 1;
|
|
int i;
|
|
for(i = 0; i < n; ++i)
|
|
{
|
|
p *= xs[i];
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/* Length of the (major, minor) bucket. Python _bucket_length. */
|
|
static int
|
|
rocke_dist_bucket_length(const rocke_tile_distribution_encoding_t* e, int major, int minor)
|
|
{
|
|
if(major == 0)
|
|
{
|
|
return e->Rs[minor];
|
|
}
|
|
return e->Hs[major - 1].levels[minor];
|
|
}
|
|
|
|
/* Python TileDistributionEncoding._validate_target. Returns false + sets sticky
|
|
* error on an out-of-range (major, minor). kind is "P"/"Y" for the message. */
|
|
static bool rocke_dist_validate_target(rocke_ir_builder_t* b,
|
|
const rocke_tile_distribution_encoding_t* e,
|
|
const char* kind,
|
|
int idx,
|
|
int major,
|
|
int minor)
|
|
{
|
|
if(major == 0)
|
|
{
|
|
if(minor < 0 || minor >= e->num_R)
|
|
{
|
|
rocke_dist_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"%s%d (R-major=0, minor=%d) out of range; Rs has %d levels",
|
|
kind,
|
|
idx,
|
|
minor,
|
|
e->num_R);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if(major < 1 || major > e->num_X)
|
|
{
|
|
rocke_dist_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"%s%d major=%d out of range (0 for R, 1..%d for X)",
|
|
kind,
|
|
idx,
|
|
major,
|
|
e->num_X);
|
|
return false;
|
|
}
|
|
if(minor < 0 || minor >= e->Hs[major - 1].count)
|
|
{
|
|
rocke_dist_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"%s%d (major=%d, minor=%d) out of range; H[%d] has %d levels",
|
|
kind,
|
|
idx,
|
|
major,
|
|
minor,
|
|
major - 1,
|
|
e->Hs[major - 1].count);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* --------------------------------------------------- encoding construction */
|
|
|
|
/* Copy an int array into the arena (NULL/0 -> NULL). */
|
|
static const int* rocke_dist_dup_ints(rocke_ir_builder_t* b, const int* src, int n)
|
|
{
|
|
int* out;
|
|
if(n <= 0 || src == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
out = (int*)rocke_arena_alloc(&b->arena, (size_t)n * sizeof(int));
|
|
if(out == NULL)
|
|
{
|
|
rocke_dist_set_err(b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
return NULL;
|
|
}
|
|
memcpy(out, src, (size_t)n * sizeof(int));
|
|
return out;
|
|
}
|
|
|
|
rocke_tile_distribution_encoding_t* rocke_make_tile_distribution_encoding(rocke_ir_builder_t* b,
|
|
const int* Rs,
|
|
int num_R,
|
|
const rocke_h_row_t* Hs,
|
|
int num_X,
|
|
const rocke_p_seq_t* Ps,
|
|
int num_P,
|
|
const int* Ys_major,
|
|
const int* Ys_minor,
|
|
int num_Y)
|
|
{
|
|
rocke_tile_distribution_encoding_t* e;
|
|
rocke_h_row_t* hs_copy = NULL;
|
|
rocke_p_seq_t* ps_copy = NULL;
|
|
int i;
|
|
int pi;
|
|
|
|
if(!rocke_dist_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* __post_init__ rank checks that are structural to our representation.
|
|
* (Python keeps Ps2RHs_major/minor as two parallel tuples; here a P dim's
|
|
* major/minor share one rocke_p_seq, so the per-P "major/minor sub-sequence
|
|
* length mismatch" is impossible by construction. We still honour the
|
|
* remaining __post_init__ semantics below.) */
|
|
|
|
e = (rocke_tile_distribution_encoding_t*)rocke_arena_calloc(
|
|
&b->arena, sizeof(rocke_tile_distribution_encoding_t));
|
|
if(e == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
|
|
e->Rs = rocke_dist_dup_ints(b, Rs, num_R);
|
|
e->num_R = num_R;
|
|
e->num_X = num_X;
|
|
e->num_P = num_P;
|
|
e->Ys_major = rocke_dist_dup_ints(b, Ys_major, num_Y);
|
|
e->Ys_minor = rocke_dist_dup_ints(b, Ys_minor, num_Y);
|
|
e->num_Y = num_Y;
|
|
|
|
if(num_X > 0)
|
|
{
|
|
hs_copy
|
|
= (rocke_h_row_t*)rocke_arena_alloc(&b->arena, (size_t)num_X * sizeof(rocke_h_row_t));
|
|
if(hs_copy == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
for(i = 0; i < num_X; ++i)
|
|
{
|
|
hs_copy[i].count = Hs[i].count;
|
|
hs_copy[i].levels = rocke_dist_dup_ints(b, Hs[i].levels, Hs[i].count);
|
|
}
|
|
}
|
|
e->Hs = hs_copy;
|
|
|
|
if(num_P > 0)
|
|
{
|
|
ps_copy
|
|
= (rocke_p_seq_t*)rocke_arena_alloc(&b->arena, (size_t)num_P * sizeof(rocke_p_seq_t));
|
|
if(ps_copy == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
for(i = 0; i < num_P; ++i)
|
|
{
|
|
ps_copy[i].count = Ps[i].count;
|
|
ps_copy[i].major = rocke_dist_dup_ints(b, Ps[i].major, Ps[i].count);
|
|
ps_copy[i].minor = rocke_dist_dup_ints(b, Ps[i].minor, Ps[i].count);
|
|
}
|
|
}
|
|
e->Ps = ps_copy;
|
|
|
|
if(!rocke_dist_live(b))
|
|
{
|
|
return NULL; /* an alloc above failed */
|
|
}
|
|
|
|
/* -- __post_init__ validation (faithful port) ------------------------- */
|
|
|
|
/* P targets in range. */
|
|
for(pi = 0; pi < num_P; ++pi)
|
|
{
|
|
int j;
|
|
for(j = 0; j < e->Ps[pi].count; ++j)
|
|
{
|
|
if(!rocke_dist_validate_target(b, e, "P", pi, e->Ps[pi].major[j], e->Ps[pi].minor[j]))
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
/* Y targets in range. */
|
|
for(i = 0; i < num_Y; ++i)
|
|
{
|
|
if(!rocke_dist_validate_target(b, e, "Y", i, e->Ys_major[i], e->Ys_minor[i]))
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Every (major, minor) bucket must be referenced exactly once. The Python
|
|
* uses a set; here the bucket space is small (R levels + sum of H levels),
|
|
* so a flat "seen" array over a compact bucket id suffices. Bucket id:
|
|
* major == 0 -> minor (R levels)
|
|
* major > 0 -> num_R + (prefix H levels) + minor (H levels)
|
|
* which is a dense bijection onto [0, total_buckets). */
|
|
{
|
|
int total_buckets = num_R;
|
|
int x;
|
|
int* h_base; /* base bucket id for X dim x's H levels */
|
|
char* seen;
|
|
int total;
|
|
|
|
h_base = NULL;
|
|
if(num_X > 0)
|
|
{
|
|
h_base = (int*)rocke_arena_alloc(&b->arena, (size_t)num_X * sizeof(int));
|
|
if(h_base == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
for(x = 0; x < num_X; ++x)
|
|
{
|
|
h_base[x] = total_buckets;
|
|
total_buckets += e->Hs[x].count;
|
|
}
|
|
total = total_buckets;
|
|
seen = (char*)rocke_arena_calloc(&b->arena, (size_t)(total > 0 ? total : 1));
|
|
if(seen == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
|
|
#define ROCKE_DIST_BUCKET_ID(maj, mnr) ((maj) == 0 ? (mnr) : (h_base[(maj) - 1] + (mnr)))
|
|
|
|
for(pi = 0; pi < num_P; ++pi)
|
|
{
|
|
int j;
|
|
for(j = 0; j < e->Ps[pi].count; ++j)
|
|
{
|
|
int id = ROCKE_DIST_BUCKET_ID(e->Ps[pi].major[j], e->Ps[pi].minor[j]);
|
|
if(seen[id])
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b,
|
|
ROCKE_ERR_VALUE,
|
|
"bucket (%d,%d) referenced by multiple P/Y entries",
|
|
e->Ps[pi].major[j],
|
|
e->Ps[pi].minor[j]);
|
|
}
|
|
seen[id] = 1;
|
|
}
|
|
}
|
|
for(i = 0; i < num_Y; ++i)
|
|
{
|
|
int id = ROCKE_DIST_BUCKET_ID(e->Ys_major[i], e->Ys_minor[i]);
|
|
if(seen[id])
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b,
|
|
ROCKE_ERR_VALUE,
|
|
"bucket (%d,%d) referenced by multiple P/Y entries",
|
|
e->Ys_major[i],
|
|
e->Ys_minor[i]);
|
|
}
|
|
seen[id] = 1;
|
|
}
|
|
|
|
/* Coverage: every H bucket must be referenced. */
|
|
for(x = 0; x < num_X; ++x)
|
|
{
|
|
int level;
|
|
for(level = 0; level < e->Hs[x].count; ++level)
|
|
{
|
|
if(!seen[h_base[x] + level])
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b,
|
|
ROCKE_ERR_VALUE,
|
|
"H bucket X%d level %d has no P or Y contributor",
|
|
x,
|
|
level);
|
|
}
|
|
}
|
|
}
|
|
/* Coverage: every R bucket should be referenced too. */
|
|
for(i = 0; i < num_R; ++i)
|
|
{
|
|
if(!seen[i])
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_VALUE, "R bucket level %d has no P or Y contributor", i);
|
|
}
|
|
}
|
|
|
|
#undef ROCKE_DIST_BUCKET_ID
|
|
}
|
|
|
|
/* Silence unused-helper warnings in builds where the bucket-length/_prod
|
|
* parity helpers are not otherwise referenced. They mirror Python
|
|
* properties and are kept for fidelity. */
|
|
(void)rocke_dist_bucket_length;
|
|
(void)rocke_dist_prod;
|
|
|
|
return e;
|
|
}
|
|
|
|
/* ------------------------------------------- make_static_tile_distribution */
|
|
|
|
rocke_tile_distribution_t*
|
|
rocke_make_static_tile_distribution(rocke_ir_builder_t* b,
|
|
const rocke_tile_distribution_encoding_t* encoding)
|
|
{
|
|
rocke_tile_distribution_t* dist;
|
|
rocke_h_bucket_ref_t** rows; /* [num_X] -> rocke_h_bucket_ref_t[Hs[x].count] */
|
|
int num_X;
|
|
int x;
|
|
int pi;
|
|
int yi;
|
|
|
|
if(!rocke_dist_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
if(encoding == NULL)
|
|
{
|
|
return (rocke_tile_distribution_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_VALUE, "make_static_tile_distribution: encoding is NULL");
|
|
}
|
|
|
|
num_X = encoding->num_X;
|
|
|
|
/* contributors = [[None for _ in hs] for hs in encoding.Hs] */
|
|
rows = NULL;
|
|
if(num_X > 0)
|
|
{
|
|
rows = (rocke_h_bucket_ref_t**)rocke_arena_alloc(
|
|
&b->arena, (size_t)num_X * sizeof(rocke_h_bucket_ref_t*));
|
|
if(rows == NULL)
|
|
{
|
|
return (rocke_tile_distribution_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
for(x = 0; x < num_X; ++x)
|
|
{
|
|
int n = encoding->Hs[x].count;
|
|
/* calloc => every ref starts kind == '\0' (== Python None). */
|
|
rows[x] = (rocke_h_bucket_ref_t*)rocke_arena_calloc(
|
|
&b->arena, (size_t)(n > 0 ? n : 1) * sizeof(rocke_h_bucket_ref_t));
|
|
if(rows[x] == NULL)
|
|
{
|
|
return (rocke_tile_distribution_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
|
|
/* Ps cover their entries first; Ys fill any remaining holes. */
|
|
for(pi = 0; pi < encoding->num_P; ++pi)
|
|
{
|
|
int inner;
|
|
const rocke_p_seq_t* p = &encoding->Ps[pi];
|
|
for(inner = 0; inner < p->count; ++inner)
|
|
{
|
|
int maj = p->major[inner];
|
|
int minor = p->minor[inner];
|
|
if(maj == 0)
|
|
{
|
|
continue; /* R contributors don't enter X */
|
|
}
|
|
x = maj - 1;
|
|
rows[x][minor].kind = 'P';
|
|
rows[x][minor].outer_idx = pi;
|
|
rows[x][minor].inner_idx = inner;
|
|
}
|
|
}
|
|
for(yi = 0; yi < encoding->num_Y; ++yi)
|
|
{
|
|
int maj = encoding->Ys_major[yi];
|
|
int minor = encoding->Ys_minor[yi];
|
|
if(maj == 0)
|
|
{
|
|
continue; /* R contributor; not in X */
|
|
}
|
|
x = maj - 1;
|
|
rows[x][minor].kind = 'Y';
|
|
rows[x][minor].outer_idx = yi;
|
|
rows[x][minor].inner_idx = 0;
|
|
}
|
|
|
|
/* Defensive: every H bucket must have a contributor (Python re-checks even
|
|
* though the encoding validation already covers it). */
|
|
for(x = 0; x < num_X; ++x)
|
|
{
|
|
int level;
|
|
for(level = 0; level < encoding->Hs[x].count; ++level)
|
|
{
|
|
if(rows[x][level].kind == '\0')
|
|
{
|
|
return (rocke_tile_distribution_t*)rocke_dist_set_err(
|
|
b,
|
|
ROCKE_ERR_VALUE,
|
|
"H bucket X%d level %d unmapped after assignment",
|
|
x,
|
|
level);
|
|
}
|
|
}
|
|
}
|
|
|
|
dist = (rocke_tile_distribution_t*)rocke_arena_calloc(&b->arena,
|
|
sizeof(rocke_tile_distribution_t));
|
|
if(dist == NULL)
|
|
{
|
|
return (rocke_tile_distribution_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
dist->encoding = encoding;
|
|
dist->contributors = (const rocke_h_bucket_ref_t* const*)rows;
|
|
return dist;
|
|
}
|
|
|
|
/* TileDistribution._lookup_contributor: resolve one H-bucket ref to an SSA
|
|
* value supplied by either the ys (Y kind) or the ps sub-arrays (P kind). */
|
|
static rocke_value_t* rocke_dist_lookup_contributor(const rocke_h_bucket_ref_t* ref,
|
|
rocke_value_t* const* ys,
|
|
rocke_value_t* const* const* ps)
|
|
{
|
|
if(ref->kind == 'Y')
|
|
{
|
|
return ys[ref->outer_idx];
|
|
}
|
|
/* 'P' */
|
|
return ps[ref->outer_idx][ref->inner_idx];
|
|
}
|
|
|
|
int rocke_tile_distribution_calculate_x(rocke_ir_builder_t* b,
|
|
const rocke_tile_distribution_t* dist,
|
|
rocke_value_t* const* ys,
|
|
int num_ys,
|
|
rocke_value_t* const* const* ps,
|
|
const int* ps_counts,
|
|
int num_ps,
|
|
rocke_value_t** out_x,
|
|
int out_x_cap)
|
|
{
|
|
const rocke_tile_distribution_encoding_t* enc;
|
|
int x_dim;
|
|
|
|
(void)ps_counts;
|
|
if(!rocke_dist_live(b) || dist == NULL || dist->encoding == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
enc = dist->encoding;
|
|
if(num_ys != enc->num_Y || num_ps != enc->num_P || out_x_cap < enc->num_X)
|
|
{
|
|
/* Python raises ValueError on rank mismatch. */
|
|
rocke_dist_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"calculate_x: rank mismatch (ys=%d/%d ps=%d/%d x_cap=%d/%d)",
|
|
num_ys,
|
|
enc->num_Y,
|
|
num_ps,
|
|
enc->num_P,
|
|
out_x_cap,
|
|
enc->num_X);
|
|
return 0;
|
|
}
|
|
|
|
for(x_dim = 0; x_dim < enc->num_X; ++x_dim)
|
|
{
|
|
const rocke_h_row_t* hs = &enc->Hs[x_dim];
|
|
/* x = b.const_i32(0); stride = 1; walk innermost (small stride) outward. */
|
|
rocke_value_t* x = rocke_b_const_i32(b, 0);
|
|
int stride = 1;
|
|
int level;
|
|
for(level = hs->count - 1; level >= 0; --level)
|
|
{
|
|
const rocke_h_bucket_ref_t* ref = &dist->contributors[x_dim][level];
|
|
rocke_value_t* contributor = rocke_dist_lookup_contributor(ref, ys, ps);
|
|
if(stride == 1)
|
|
{
|
|
x = rocke_b_add(b, x, contributor);
|
|
}
|
|
else
|
|
{
|
|
x = rocke_b_add(b, x, rocke_b_mul(b, contributor, rocke_b_const_i32(b, stride)));
|
|
}
|
|
stride *= hs->levels[level];
|
|
}
|
|
out_x[x_dim] = x;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* Reduce distribution + static distributed tensor + block_tile_reduce_sync.
|
|
* Ports of distribution.py: make_reduce_tile_distribution_encoding,
|
|
* make_static_distributed_tensor, _lane_p_index, _r_butterfly_plan,
|
|
* block_tile_reduce_sync.
|
|
* ========================================================================== */
|
|
|
|
/* TileDistributionEncoding.num_elements_per_thread == prod(Y_lengths), where a Y
|
|
* mapped to R (major==0) draws its length from Rs, otherwise from Hs. Mirrors
|
|
* the Python Y_lengths / num_elements_per_thread properties. */
|
|
static int rocke_dist_num_elements_per_thread(const rocke_tile_distribution_encoding_t* e)
|
|
{
|
|
int prod = 1;
|
|
int i;
|
|
for(i = 0; i < e->num_Y; ++i)
|
|
{
|
|
prod *= rocke_dist_bucket_length(e, e->Ys_major[i], e->Ys_minor[i]);
|
|
}
|
|
return prod;
|
|
}
|
|
|
|
/* bit_length()-1 of a positive int (== floor(log2(n)) for a power of two). */
|
|
static int rocke_dist_bit_length_m1(int n)
|
|
{
|
|
int bits = 0;
|
|
while(n > 0)
|
|
{
|
|
++bits;
|
|
n >>= 1;
|
|
}
|
|
return bits - 1;
|
|
}
|
|
|
|
rocke_tile_distribution_encoding_t*
|
|
rocke_make_reduce_tile_distribution_encoding(rocke_ir_builder_t* b,
|
|
const rocke_tile_distribution_encoding_t* encoding,
|
|
const int* reduce_dim_xs,
|
|
int num_reduce)
|
|
{
|
|
const rocke_tile_distribution_encoding_t* e = encoding;
|
|
int num_x_in;
|
|
int num_rh_major; /* num_x_in + 1 */
|
|
char* is_rh_major_for_reduce = NULL; /* [num_rh_major] */
|
|
char* is_y_for_reduce = NULL; /* [num_Y] */
|
|
int* in2out_rh_major = NULL; /* [num_rh_major] */
|
|
/* in2out_rh_minor stored as a flat table keyed by (major, minor). We use a
|
|
* per-major base offset into a dense array. */
|
|
int* minor_base = NULL; /* [num_rh_major]: base index for (major,*) */
|
|
int* in2out_rh_minor = NULL; /* dense */
|
|
char* consumed_by_reduced_y = NULL; /* dense, same indexing as minor */
|
|
int* rs_lengths_out = NULL;
|
|
int rs_count = 0;
|
|
rocke_h_row_t* hs_out = NULL;
|
|
int hs_count = 0;
|
|
rocke_p_seq_t* ps_out = NULL;
|
|
int* ys_major_out = NULL;
|
|
int* ys_minor_out = NULL;
|
|
int ys_count = 0;
|
|
int total_minor = 0;
|
|
int i, r;
|
|
int cnt_major_out;
|
|
int cnt_r_out;
|
|
rocke_tile_distribution_encoding_t* out = NULL;
|
|
|
|
if(!rocke_dist_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
if(e == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_VALUE, "make_reduce_tile_distribution_encoding: NULL encoding");
|
|
}
|
|
|
|
num_x_in = e->num_X;
|
|
num_rh_major = num_x_in + 1;
|
|
|
|
for(i = 0; i < num_reduce; ++i)
|
|
{
|
|
int d = reduce_dim_xs[i];
|
|
if(d < 0 || d >= num_x_in)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_VALUE, "reduce_dim %d out of range (num_X=%d)", d, num_x_in);
|
|
}
|
|
}
|
|
|
|
is_rh_major_for_reduce = (char*)rocke_arena_calloc(&b->arena, (size_t)num_rh_major);
|
|
in2out_rh_major = (int*)rocke_arena_alloc(&b->arena, (size_t)num_rh_major * sizeof(int));
|
|
minor_base = (int*)rocke_arena_alloc(&b->arena, (size_t)num_rh_major * sizeof(int));
|
|
if(is_rh_major_for_reduce == NULL || in2out_rh_major == NULL || minor_base == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
for(i = 0; i < num_reduce; ++i)
|
|
{
|
|
is_rh_major_for_reduce[reduce_dim_xs[i] + 1] = 1;
|
|
}
|
|
|
|
/* Dense (major,minor) index space: major 0 (R) has num_R minors; major k>0
|
|
* has Hs[k-1].count minors. minor_base[major] = prefix sum. */
|
|
minor_base[0] = 0;
|
|
total_minor = e->num_R;
|
|
for(i = 1; i < num_rh_major; ++i)
|
|
{
|
|
minor_base[i] = total_minor;
|
|
total_minor += e->Hs[i - 1].count;
|
|
}
|
|
if(total_minor <= 0)
|
|
{
|
|
total_minor = 1;
|
|
}
|
|
in2out_rh_minor = (int*)rocke_arena_alloc(&b->arena, (size_t)total_minor * sizeof(int));
|
|
consumed_by_reduced_y = (char*)rocke_arena_calloc(&b->arena, (size_t)total_minor);
|
|
if(in2out_rh_minor == NULL || consumed_by_reduced_y == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
|
|
/* is_y_for_reduce[i] = is_rh_major_for_reduce[Ys_major[i]] */
|
|
if(e->num_Y > 0)
|
|
{
|
|
is_y_for_reduce = (char*)rocke_arena_calloc(&b->arena, (size_t)e->num_Y);
|
|
if(is_y_for_reduce == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
for(i = 0; i < e->num_Y; ++i)
|
|
{
|
|
is_y_for_reduce[i] = is_rh_major_for_reduce[e->Ys_major[i]];
|
|
if(is_y_for_reduce[i])
|
|
{
|
|
int mb = minor_base[e->Ys_major[i]] + e->Ys_minor[i];
|
|
consumed_by_reduced_y[mb] = 1;
|
|
}
|
|
}
|
|
|
|
/* in2out_rh_major: reduced X -> 0 (R); surviving X -> dense 1..K. */
|
|
cnt_major_out = 0;
|
|
for(i = 0; i < num_rh_major; ++i)
|
|
{
|
|
if(is_rh_major_for_reduce[i])
|
|
{
|
|
in2out_rh_major[i] = 0;
|
|
}
|
|
else
|
|
{
|
|
in2out_rh_major[i] = cnt_major_out;
|
|
++cnt_major_out;
|
|
}
|
|
}
|
|
|
|
/* rs_lengths_out + in2out_rh_minor. Upper bound on rs_count = num_R + all H
|
|
* levels of reduced X dims. */
|
|
rs_lengths_out = (int*)rocke_arena_alloc(
|
|
&b->arena, (size_t)(total_minor > 0 ? total_minor : 1) * sizeof(int));
|
|
if(rs_lengths_out == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
rs_count = 0;
|
|
/* input R levels carry over unchanged. */
|
|
for(i = 0; i < e->num_R; ++i)
|
|
{
|
|
rs_lengths_out[rs_count] = e->Rs[i];
|
|
in2out_rh_minor[minor_base[0] + i] = i;
|
|
++rs_count;
|
|
}
|
|
cnt_r_out = e->num_R;
|
|
for(i = 1; i < num_rh_major; ++i)
|
|
{
|
|
const rocke_h_row_t* h = &e->Hs[i - 1];
|
|
int level;
|
|
if(is_rh_major_for_reduce[i])
|
|
{
|
|
for(level = 0; level < h->count; ++level)
|
|
{
|
|
int mb = minor_base[i] + level;
|
|
if(!consumed_by_reduced_y[mb])
|
|
{
|
|
rs_lengths_out[rs_count] = h->levels[level];
|
|
++rs_count;
|
|
in2out_rh_minor[mb] = cnt_r_out;
|
|
++cnt_r_out;
|
|
}
|
|
/* else: consumed by a reduced Y -> folded away entirely. */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(level = 0; level < h->count; ++level)
|
|
{
|
|
in2out_rh_minor[minor_base[i] + level] = level;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Surviving X dims keep their H decomposition. */
|
|
if(cnt_major_out > 1)
|
|
{
|
|
hs_out = (rocke_h_row_t*)rocke_arena_alloc(
|
|
&b->arena, (size_t)(cnt_major_out - 1) * sizeof(rocke_h_row_t));
|
|
if(hs_out == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
hs_count = 0;
|
|
for(i = 0; i < num_x_in; ++i)
|
|
{
|
|
if(!is_rh_major_for_reduce[i + 1])
|
|
{
|
|
hs_out[hs_count] = e->Hs[i]; /* arena-owned levels reused */
|
|
++hs_count;
|
|
}
|
|
}
|
|
|
|
/* P dims: re-point every contribution. */
|
|
if(e->num_P > 0)
|
|
{
|
|
ps_out = (rocke_p_seq_t*)rocke_arena_alloc(&b->arena,
|
|
(size_t)e->num_P * sizeof(rocke_p_seq_t));
|
|
if(ps_out == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
for(i = 0; i < e->num_P; ++i)
|
|
{
|
|
const rocke_p_seq_t* p = &e->Ps[i];
|
|
int* nm = NULL;
|
|
int* nn = NULL;
|
|
int j;
|
|
if(p->count > 0)
|
|
{
|
|
nm = (int*)rocke_arena_alloc(&b->arena, (size_t)p->count * sizeof(int));
|
|
nn = (int*)rocke_arena_alloc(&b->arena, (size_t)p->count * sizeof(int));
|
|
if(nm == NULL || nn == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
for(j = 0; j < p->count; ++j)
|
|
{
|
|
int maj = p->major[j];
|
|
int minor = p->minor[j];
|
|
nm[j] = in2out_rh_major[maj];
|
|
nn[j] = in2out_rh_minor[minor_base[maj] + minor];
|
|
}
|
|
ps_out[i].count = p->count;
|
|
ps_out[i].major = nm;
|
|
ps_out[i].minor = nn;
|
|
}
|
|
}
|
|
|
|
/* Surviving Y dims. */
|
|
if(e->num_Y > 0)
|
|
{
|
|
ys_major_out = (int*)rocke_arena_alloc(&b->arena, (size_t)e->num_Y * sizeof(int));
|
|
ys_minor_out = (int*)rocke_arena_alloc(&b->arena, (size_t)e->num_Y * sizeof(int));
|
|
if(ys_major_out == NULL || ys_minor_out == NULL)
|
|
{
|
|
return (rocke_tile_distribution_encoding_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
}
|
|
ys_count = 0;
|
|
for(i = 0; i < e->num_Y; ++i)
|
|
{
|
|
if(!is_y_for_reduce[i])
|
|
{
|
|
int maj = e->Ys_major[i];
|
|
int minor = e->Ys_minor[i];
|
|
ys_major_out[ys_count] = in2out_rh_major[maj];
|
|
ys_minor_out[ys_count] = in2out_rh_minor[minor_base[maj] + minor];
|
|
++ys_count;
|
|
}
|
|
}
|
|
|
|
(void)r;
|
|
out = rocke_make_tile_distribution_encoding(b,
|
|
rs_count > 0 ? rs_lengths_out : NULL,
|
|
rs_count,
|
|
hs_count > 0 ? hs_out : NULL,
|
|
hs_count,
|
|
e->num_P > 0 ? ps_out : NULL,
|
|
e->num_P,
|
|
ys_count > 0 ? ys_major_out : NULL,
|
|
ys_count > 0 ? ys_minor_out : NULL,
|
|
ys_count);
|
|
return out;
|
|
}
|
|
|
|
rocke_static_distributed_tensor_t* rocke_make_static_distributed_tensor(
|
|
rocke_ir_builder_t* b, const rocke_tile_distribution_t* distribution, const rocke_type_t* dtype)
|
|
{
|
|
rocke_static_distributed_tensor_t* t;
|
|
int n;
|
|
|
|
if(!rocke_dist_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
if(distribution == NULL || distribution->encoding == NULL)
|
|
{
|
|
return (rocke_static_distributed_tensor_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_VALUE, "make_static_distributed_tensor: NULL distribution");
|
|
}
|
|
n = rocke_dist_num_elements_per_thread(distribution->encoding);
|
|
if(n < 1)
|
|
{
|
|
n = 1;
|
|
}
|
|
t = (rocke_static_distributed_tensor_t*)rocke_arena_calloc(
|
|
&b->arena, sizeof(rocke_static_distributed_tensor_t));
|
|
if(t == NULL)
|
|
{
|
|
return (rocke_static_distributed_tensor_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
t->distribution = distribution;
|
|
t->dtype = dtype;
|
|
t->num_storage = n;
|
|
t->storage = (rocke_value_t**)rocke_arena_calloc(&b->arena, (size_t)n * sizeof(rocke_value_t*));
|
|
if(t->storage == NULL)
|
|
{
|
|
return (rocke_static_distributed_tensor_t*)rocke_dist_set_err(
|
|
b, ROCKE_ERR_OOM, "distribution: arena alloc failed");
|
|
}
|
|
return t;
|
|
}
|
|
|
|
/* _r_butterfly_plan(encoding, p_idx): for the R levels P dim p_idx owns, emit
|
|
* (r_length, derivative) ascending by R-level. Mirrors distribution.py
|
|
* _r_butterfly_plan / p_feeds_r. Writes up to ``cap`` entries; returns count. */
|
|
typedef struct rocke_r_plan_entry
|
|
{
|
|
int r_length;
|
|
int derivative;
|
|
} rocke_r_plan_entry_t;
|
|
|
|
static int rocke_dist_r_butterfly_plan(const rocke_tile_distribution_encoding_t* enc,
|
|
int p_idx,
|
|
rocke_r_plan_entry_t* out,
|
|
int cap)
|
|
{
|
|
/* owned = [(inner_idx, r_minor)] for contributions with major==0. */
|
|
int r_minors[64];
|
|
int lengths[64];
|
|
int owned = 0;
|
|
int k, j;
|
|
const rocke_p_seq_t* p;
|
|
|
|
if(p_idx < 0 || p_idx >= enc->num_P)
|
|
{
|
|
return 0;
|
|
}
|
|
p = &enc->Ps[p_idx];
|
|
for(k = 0; k < p->count; ++k)
|
|
{
|
|
if(p->major[k] == 0)
|
|
{
|
|
int rm = p->minor[k];
|
|
if(owned < 64)
|
|
{
|
|
r_minors[owned] = rm;
|
|
lengths[owned] = enc->Rs[rm];
|
|
++owned;
|
|
}
|
|
}
|
|
}
|
|
if(owned == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
/* derivative[k] = product of lengths of owned R levels AFTER k (in
|
|
* contribution order). Then sort entries by r-level ascending. */
|
|
{
|
|
/* Build (r_minor, deriv, length) then selection-sort by r_minor. */
|
|
int rm[64];
|
|
int dv[64];
|
|
int ln[64];
|
|
int cnt = owned;
|
|
for(k = 0; k < cnt; ++k)
|
|
{
|
|
int deriv = 1;
|
|
for(j = k + 1; j < cnt; ++j)
|
|
{
|
|
deriv *= lengths[j];
|
|
}
|
|
rm[k] = r_minors[k];
|
|
dv[k] = deriv;
|
|
ln[k] = lengths[k];
|
|
}
|
|
/* stable selection sort by rm */
|
|
for(k = 0; k < cnt; ++k)
|
|
{
|
|
int best = k;
|
|
for(j = k + 1; j < cnt; ++j)
|
|
{
|
|
if(rm[j] < rm[best])
|
|
{
|
|
best = j;
|
|
}
|
|
}
|
|
if(best != k)
|
|
{
|
|
int tr = rm[k];
|
|
rm[k] = rm[best];
|
|
rm[best] = tr;
|
|
int td = dv[k];
|
|
dv[k] = dv[best];
|
|
dv[best] = td;
|
|
int tl = ln[k];
|
|
ln[k] = ln[best];
|
|
ln[best] = tl;
|
|
}
|
|
}
|
|
{
|
|
int n = cnt < cap ? cnt : cap;
|
|
for(k = 0; k < n; ++k)
|
|
{
|
|
out[k].r_length = ln[k];
|
|
out[k].derivative = dv[k];
|
|
}
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
static rocke_value_t* rocke_dist_fold(rocke_ir_builder_t* b,
|
|
rocke_reduce_combine_t combine,
|
|
rocke_value_t* x,
|
|
rocke_value_t* y)
|
|
{
|
|
return combine == ROCKE_REDUCE_SUM ? rocke_b_fadd(b, x, y) : rocke_b_fmax(b, x, y);
|
|
}
|
|
|
|
void rocke_block_tile_reduce_sync(rocke_ir_builder_t* b,
|
|
rocke_static_distributed_tensor_t* reduced,
|
|
rocke_reduce_combine_t combine,
|
|
rocke_value_t* lds_buf,
|
|
rocke_value_t* tid,
|
|
int wave_size)
|
|
{
|
|
const rocke_tile_distribution_encoding_t* enc;
|
|
rocke_r_plan_entry_t lane_plan[64];
|
|
rocke_r_plan_entry_t warp_plan[64];
|
|
int lane_p;
|
|
int n_lane;
|
|
int n_warp;
|
|
int off;
|
|
int num_reduce_warps;
|
|
int thread_buf_size;
|
|
int i;
|
|
|
|
if(!rocke_dist_live(b))
|
|
{
|
|
return;
|
|
}
|
|
if(combine != ROCKE_REDUCE_SUM && combine != ROCKE_REDUCE_MAX)
|
|
{
|
|
rocke_dist_set_err(b, ROCKE_ERR_VALUE, "combine must be 'sum' or 'max'");
|
|
return;
|
|
}
|
|
if(reduced == NULL || reduced->distribution == NULL || reduced->distribution->encoding == NULL)
|
|
{
|
|
rocke_dist_set_err(b, ROCKE_ERR_VALUE, "block_tile_reduce_sync: NULL reduced");
|
|
return;
|
|
}
|
|
enc = reduced->distribution->encoding;
|
|
if(enc->num_P == 0)
|
|
{
|
|
rocke_dist_set_err(
|
|
b, ROCKE_ERR_VALUE, "block_tile_reduce_sync requires at least one P dim");
|
|
return;
|
|
}
|
|
|
|
/* -- Stage 1: warp-local XOR butterfly over lane-owned R levels -------- */
|
|
lane_p = enc->num_P - 1; /* _lane_p_index */
|
|
n_lane = rocke_dist_r_butterfly_plan(enc, lane_p, lane_plan, 64);
|
|
for(off = 0; off < reduced->num_storage; ++off)
|
|
{
|
|
rocke_value_t* v_local = reduced->storage[off];
|
|
int pi;
|
|
if(v_local == NULL)
|
|
{
|
|
rocke_dist_set_err(b, ROCKE_ERR_VALUE, "reduce slot %d not initialised", off);
|
|
return;
|
|
}
|
|
for(pi = 0; pi < n_lane; ++pi)
|
|
{
|
|
int r_length = lane_plan[pi].r_length;
|
|
int derivative = lane_plan[pi].derivative;
|
|
int stages = rocke_dist_bit_length_m1(r_length);
|
|
int istage;
|
|
for(istage = 0; istage < stages; ++istage)
|
|
{
|
|
rocke_value_t* remote = rocke_b_warp_shuffle_xor(b, v_local, derivative << istage);
|
|
v_local = rocke_dist_fold(b, combine, v_local, remote);
|
|
}
|
|
}
|
|
reduced->storage[off] = v_local;
|
|
}
|
|
|
|
/* -- Stage 2: cross-warp LDS over warp-owned R levels ------------------ */
|
|
n_warp = (enc->num_P >= 2) ? rocke_dist_r_butterfly_plan(enc, 0, warp_plan, 64) : 0;
|
|
num_reduce_warps = 1;
|
|
for(i = 0; i < n_warp; ++i)
|
|
{
|
|
num_reduce_warps *= warp_plan[i].r_length;
|
|
}
|
|
if(num_reduce_warps <= 1)
|
|
{
|
|
return;
|
|
}
|
|
if(lds_buf == NULL || tid == NULL)
|
|
{
|
|
rocke_dist_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"block_tile_reduce_sync needs lds_buf + tid for cross-warp "
|
|
"reduce (num_reduce_warps=%d)",
|
|
num_reduce_warps);
|
|
return;
|
|
}
|
|
|
|
{
|
|
rocke_value_t* c_wave = rocke_b_const_i32(b, wave_size);
|
|
rocke_value_t* lane = rocke_b_mod(b, tid, c_wave);
|
|
rocke_value_t* warp = rocke_b_div(b, tid, c_wave);
|
|
rocke_value_t* local_warp_id;
|
|
rocke_value_t* local_smem_os;
|
|
|
|
thread_buf_size = reduced->num_storage;
|
|
|
|
/* if lane == 0: publish thread buffer interleaved by warp. */
|
|
{
|
|
rocke_if_t iff = rocke_b_scf_if(b, rocke_b_cmp_eq(b, lane, rocke_b_const_i32(b, 0)));
|
|
rocke_b_region_enter(b, iff.then_region);
|
|
for(i = 0; i < thread_buf_size; ++i)
|
|
{
|
|
rocke_value_t* v = reduced->storage[i];
|
|
rocke_value_t* slot
|
|
= rocke_b_add(b, warp, rocke_b_const_i32(b, i * num_reduce_warps));
|
|
rocke_value_t* idx[1];
|
|
idx[0] = slot;
|
|
rocke_b_smem_store_vN_f32(b, lds_buf, idx, 1, v, 1);
|
|
}
|
|
rocke_b_region_leave(b);
|
|
}
|
|
rocke_b_sync(b);
|
|
|
|
local_warp_id = rocke_b_div(b, warp, rocke_b_const_i32(b, num_reduce_warps));
|
|
local_smem_os = rocke_b_mul(b, local_warp_id, rocke_b_const_i32(b, num_reduce_warps));
|
|
for(i = 0; i < thread_buf_size; ++i)
|
|
{
|
|
rocke_value_t* parts[256];
|
|
int np = num_reduce_warps;
|
|
int idx_i;
|
|
for(idx_i = 0; idx_i < num_reduce_warps; ++idx_i)
|
|
{
|
|
rocke_value_t* slot = rocke_b_add(
|
|
b, local_smem_os, rocke_b_const_i32(b, i * num_reduce_warps + idx_i));
|
|
rocke_value_t* sidx[1];
|
|
rocke_value_t* vec;
|
|
sidx[0] = slot;
|
|
vec = rocke_b_smem_load_vN_f32(b, lds_buf, sidx, 1, 1);
|
|
parts[idx_i] = rocke_b_vec_extract(b, vec, 0);
|
|
}
|
|
/* Pairwise power-of-two tree fold. */
|
|
while(np > 1)
|
|
{
|
|
int j;
|
|
int m = 0;
|
|
for(j = 0; j < np; j += 2)
|
|
{
|
|
parts[m++] = rocke_dist_fold(b, combine, parts[j], parts[j + 1]);
|
|
}
|
|
np = m;
|
|
}
|
|
reduced->storage[i] = parts[0];
|
|
}
|
|
}
|
|
}
|