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
408 lines
13 KiB
C++
408 lines
13 KiB
C++
// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* helper_rocke.helpers.io.c -- C99 port of rocke.helpers.io.io_ir_type,
|
|
* store_scalar_from_f32, load_scalar, load_scalar_as_f32, load_vec,
|
|
* load_vec_as_f32, load_lane_slice_f32, store_vec, pack_f32_to and
|
|
* vector_row_copy.
|
|
*
|
|
* Faithful translation of:
|
|
*
|
|
* def io_ir_type(dtype: str) -> Type:
|
|
* if dtype in ("f16", "fp16"): return F16
|
|
* if dtype == "bf16": return BF16
|
|
* raise ValueError(
|
|
* f"unsupported I/O dtype {dtype!r}; expected f16/fp16/bf16")
|
|
*
|
|
* Mapping invariants (must stay byte-identical to the Python so downstream IR is
|
|
* identical):
|
|
* "f16" -> rocke_f16() (Python F16)
|
|
* "fp16" -> rocke_f16() (alias; Python F16)
|
|
* "bf16" -> rocke_bf16() (Python BF16)
|
|
* else -> NULL (Python ValueError)
|
|
*/
|
|
|
|
#include "rocke/helper_rocke.helpers.io.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "rocke/arena.h" /* rocke_arena_alloc */
|
|
#include "rocke/ir_internal.h" /* rocke_i_set_err, rocke_i_live */
|
|
|
|
/* Power-of-two vector widths the DSL's global_load_vN covers. Mirrors the
|
|
* module-level _VEC_WIDTHS = (2, 4, 8) in rocke.helpers.io. */
|
|
static int rocke_io_is_vec_width(int n)
|
|
{
|
|
return n == 2 || n == 4 || n == 8;
|
|
}
|
|
|
|
const rocke_type_t* rocke_io_ir_type(const char* dtype)
|
|
{
|
|
if(dtype == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* `dtype in ("f16", "fp16")` -> F16 */
|
|
if(strcmp(dtype, "f16") == 0 || strcmp(dtype, "fp16") == 0)
|
|
{
|
|
return rocke_f16();
|
|
}
|
|
/* `dtype == "bf16"` -> BF16 */
|
|
if(strcmp(dtype, "bf16") == 0)
|
|
{
|
|
return rocke_bf16();
|
|
}
|
|
/* Python: raise ValueError. No builder here, so signal via NULL. */
|
|
return NULL;
|
|
}
|
|
|
|
const rocke_type_t* rocke_b_io_ir_type(rocke_ir_builder_t* b, const char* dtype)
|
|
{
|
|
const rocke_type_t* ty;
|
|
|
|
/* Sticky-error model: a failed builder makes every call a NULL no-op. */
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
ty = rocke_io_ir_type(dtype);
|
|
if(ty == NULL)
|
|
{
|
|
/* Mirror the Python ValueError, including the {dtype!r} single-quote
|
|
* repr for the (non-NULL) string case. NULL is reported as "None" to
|
|
* match Python's repr(None). */
|
|
return (const rocke_type_t*)rocke_i_set_err(
|
|
b,
|
|
ROCKE_ERR_VALUE,
|
|
"unsupported I/O dtype %s%s%s; expected f16/fp16/bf16",
|
|
dtype ? "'" : "",
|
|
dtype ? dtype : "None",
|
|
dtype ? "'" : "");
|
|
}
|
|
return ty;
|
|
}
|
|
|
|
void rocke_b_store_scalar_from_f32(rocke_ir_builder_t* b,
|
|
rocke_value_t* ptr,
|
|
rocke_value_t* idx,
|
|
rocke_value_t* value_f32,
|
|
const char* dtype)
|
|
{
|
|
const rocke_type_t* target;
|
|
rocke_value_t* cast;
|
|
|
|
/* Sticky-error model: a failed builder makes every call a NULL no-op. */
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Python: target = io_ir_type(dtype). Use the builder-aware variant so an
|
|
* unsupported dtype records the same ValueError on the sticky-error model;
|
|
* on failure it returns NULL and we stop (Python would have raised). */
|
|
target = rocke_b_io_ir_type(b, dtype);
|
|
if(target == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Python: b.global_store(ptr, idx, b.cast_f32_to(value_f32, target)).
|
|
* align defaults to 0 (Python global_store takes no align kwarg here). */
|
|
cast = rocke_b_cast_f32_to(b, value_f32, target);
|
|
rocke_b_global_store(b, ptr, idx, cast, 0);
|
|
}
|
|
|
|
rocke_value_t* rocke_b_load_scalar(rocke_ir_builder_t* b,
|
|
rocke_value_t* ptr,
|
|
rocke_value_t* idx,
|
|
const char* dtype)
|
|
{
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Python: if dtype in ("f16", "fp16"): return b.global_load_f16(ptr, idx) */
|
|
if(dtype != NULL && (strcmp(dtype, "f16") == 0 || strcmp(dtype, "fp16") == 0))
|
|
{
|
|
/* Python passes no align kwarg -> default. 0 selects the builder default. */
|
|
return rocke_b_global_load_f16(b, ptr, idx, 0);
|
|
}
|
|
/* Python: if dtype == "bf16": return b.global_load_bf16(ptr, idx) */
|
|
if(dtype != NULL && strcmp(dtype, "bf16") == 0)
|
|
{
|
|
return rocke_b_global_load_bf16(b, ptr, idx, 0);
|
|
}
|
|
/* Python: raise ValueError(f"unsupported I/O dtype {dtype!r}"). Note this
|
|
* message has NO ", expected ..." suffix -- it is load_scalar's own text,
|
|
* distinct from io_ir_type's. Mirror it exactly. */
|
|
return (rocke_value_t*)rocke_i_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"unsupported I/O dtype %s%s%s",
|
|
dtype ? "'" : "",
|
|
dtype ? dtype : "None",
|
|
dtype ? "'" : "");
|
|
}
|
|
|
|
rocke_value_t* rocke_b_load_scalar_as_f32(rocke_ir_builder_t* b,
|
|
rocke_value_t* ptr,
|
|
rocke_value_t* idx,
|
|
const char* dtype)
|
|
{
|
|
rocke_value_t* v;
|
|
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Python: return b.cast_to_f32(load_scalar(b, ptr, idx, dtype=dtype)).
|
|
* load_scalar records the ValueError on an unsupported dtype and returns
|
|
* NULL; cast_to_f32 of NULL on an errored builder is itself a NULL no-op. */
|
|
v = rocke_b_load_scalar(b, ptr, idx, dtype);
|
|
if(v == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
return rocke_b_cast_to_f32(b, v);
|
|
}
|
|
|
|
rocke_value_t* rocke_b_load_vec(
|
|
rocke_ir_builder_t* b, rocke_value_t* ptr, rocke_value_t* idx, const char* dtype, int n)
|
|
{
|
|
const rocke_type_t* ty;
|
|
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Python checks n FIRST: if n not in (2,4,8): raise ValueError(...). */
|
|
if(!rocke_io_is_vec_width(n))
|
|
{
|
|
return (rocke_value_t*)rocke_i_set_err(
|
|
b, ROCKE_ERR_VALUE, "load_vec n must be 2/4/8 (got %d); use load_scalar for n=1", n);
|
|
}
|
|
/* Python: ty = io_ir_type(dtype). Builder-aware: an unsupported dtype
|
|
* records io_ir_type's ValueError and returns NULL here. */
|
|
ty = rocke_b_io_ir_type(b, dtype);
|
|
if(ty == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* Python: if dtype in ("f16","fp16"): return b.global_load_vN_f16(ptr,idx,n)
|
|
* return b.global_load_vN(ptr, idx, ty, n)
|
|
* Python passes no align -> default (0). */
|
|
if(strcmp(dtype, "f16") == 0 || strcmp(dtype, "fp16") == 0)
|
|
{
|
|
return rocke_b_global_load_vN_f16(b, ptr, idx, n, 0);
|
|
}
|
|
return rocke_b_global_load_vN(b, ptr, idx, ty, n, 0);
|
|
}
|
|
|
|
int rocke_b_load_vec_as_f32(rocke_ir_builder_t* b,
|
|
rocke_value_t* ptr,
|
|
rocke_value_t* idx,
|
|
const char* dtype,
|
|
int n,
|
|
rocke_value_t** out)
|
|
{
|
|
rocke_value_t* v;
|
|
int i;
|
|
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return 0;
|
|
}
|
|
if(out == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Python: v = load_vec(b, ptr, idx, dtype=dtype, n=n) */
|
|
v = rocke_b_load_vec(b, ptr, idx, dtype, n);
|
|
if(v == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
/* Python: return [b.cast_to_f32(b.vec_extract(v, i)) for i in range(n)] */
|
|
for(i = 0; i < n; ++i)
|
|
{
|
|
out[i] = rocke_b_cast_to_f32(b, rocke_b_vec_extract(b, v, i));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int rocke_b_load_lane_slice_f32(rocke_ir_builder_t* b,
|
|
rocke_value_t* ptr,
|
|
rocke_value_t* row_base,
|
|
rocke_value_t* lane_d_base,
|
|
const char* dtype,
|
|
int ept,
|
|
rocke_value_t** out)
|
|
{
|
|
int k;
|
|
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return 0;
|
|
}
|
|
if(out == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Python: if ept in _VEC_WIDTHS:
|
|
* return load_vec_as_f32(
|
|
* b, ptr, b.add(row_base, lane_d_base), dtype=dtype, n=ept) */
|
|
if(rocke_io_is_vec_width(ept))
|
|
{
|
|
return rocke_b_load_vec_as_f32(
|
|
b, ptr, rocke_b_add(b, row_base, lane_d_base), dtype, ept, out);
|
|
}
|
|
/* Python scalar fallback:
|
|
* return [load_scalar_as_f32(
|
|
* b, ptr,
|
|
* b.add(row_base, b.add(lane_d_base, b.const_i32(k))),
|
|
* dtype=dtype)
|
|
* for k in range(ept)] */
|
|
for(k = 0; k < ept; ++k)
|
|
{
|
|
rocke_value_t* addr
|
|
= rocke_b_add(b, row_base, rocke_b_add(b, lane_d_base, rocke_b_const_i32(b, k)));
|
|
out[k] = rocke_b_load_scalar_as_f32(b, ptr, addr, dtype);
|
|
if(out[k] == NULL)
|
|
{
|
|
/* load_scalar_as_f32 recorded the ValueError already. */
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void rocke_b_store_vec(
|
|
rocke_ir_builder_t* b, rocke_value_t* ptr, rocke_value_t* idx, rocke_value_t* value, int n)
|
|
{
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Python: if n not in (2,4,8): raise ValueError(...) */
|
|
if(!rocke_io_is_vec_width(n))
|
|
{
|
|
(void)rocke_i_set_err(
|
|
b, ROCKE_ERR_VALUE, "store_vec n must be 2/4/8 (got %d); use store_scalar for n=1", n);
|
|
return;
|
|
}
|
|
/* Python: b.global_store_vN(ptr, idx, value, n). No align -> default (0). */
|
|
rocke_b_global_store_vN(b, ptr, idx, value, n, 0);
|
|
}
|
|
|
|
rocke_value_t* rocke_b_pack_f32_to(rocke_ir_builder_t* b,
|
|
rocke_value_t* const* scalars_f32,
|
|
int n,
|
|
const char* dtype)
|
|
{
|
|
const rocke_type_t* target;
|
|
rocke_value_t** casts;
|
|
int i;
|
|
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Python: target = io_ir_type(dtype). Builder-aware -> records ValueError
|
|
* on an unsupported dtype and returns NULL. */
|
|
target = rocke_b_io_ir_type(b, dtype);
|
|
if(target == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* Python: casts = [b.cast_f32_to(v, target) for v in scalars_f32].
|
|
* Build the cast list in the arena so vec_pack gets a contiguous array. */
|
|
casts = (rocke_value_t**)rocke_arena_alloc(&b->arena, (size_t)n * sizeof(*casts));
|
|
if(casts == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
for(i = 0; i < n; ++i)
|
|
{
|
|
casts[i] = rocke_b_cast_f32_to(b, scalars_f32[i], target);
|
|
}
|
|
/* Python: return b.vec_pack(casts, target). */
|
|
return rocke_b_vec_pack(b, casts, n, target);
|
|
}
|
|
|
|
void rocke_b_vector_row_copy(rocke_ir_builder_t* b,
|
|
rocke_value_t* src,
|
|
rocke_value_t* dst,
|
|
rocke_value_t* src_base,
|
|
rocke_value_t* dst_base,
|
|
int H,
|
|
const char* dtype,
|
|
int vec_bytes)
|
|
{
|
|
const rocke_type_t* ty;
|
|
const int elem_bytes = 2; /* f16 / bf16 storage width */
|
|
int vec;
|
|
int n_chunks;
|
|
int c;
|
|
int d;
|
|
|
|
if(!rocke_i_live(b))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Python: ty = io_ir_type(dtype). Builder-aware -> records ValueError on an
|
|
* unsupported dtype and returns NULL. */
|
|
ty = rocke_b_io_ir_type(b, dtype);
|
|
if(ty == NULL)
|
|
{
|
|
return;
|
|
}
|
|
/* Python: vec = vec_bytes // elem_bytes; if vec not in (2,4,8): raise. */
|
|
vec = vec_bytes / elem_bytes;
|
|
if(!rocke_io_is_vec_width(vec))
|
|
{
|
|
(void)rocke_i_set_err(b,
|
|
ROCKE_ERR_VALUE,
|
|
"vector_row_copy: vec_bytes %d maps to vec=%d; expected 4/8/16-byte "
|
|
"aligned",
|
|
vec_bytes,
|
|
vec);
|
|
return;
|
|
}
|
|
/* Python: n_chunks = H // vec
|
|
* for c in range(n_chunks):
|
|
* d = c * vec
|
|
* src_addr = b.add(src_base, b.const_i32(d))
|
|
* dst_addr = b.add(dst_base, b.const_i32(d))
|
|
* v = b.global_load_vN(src, src_addr, ty, vec, align=vec*elem_bytes)
|
|
* b.global_store_vN(dst, dst_addr, v, vec, align=vec*elem_bytes) */
|
|
n_chunks = H / vec;
|
|
for(c = 0; c < n_chunks; ++c)
|
|
{
|
|
int dd = c * vec;
|
|
rocke_value_t* src_addr = rocke_b_add(b, src_base, rocke_b_const_i32(b, dd));
|
|
rocke_value_t* dst_addr = rocke_b_add(b, dst_base, rocke_b_const_i32(b, dd));
|
|
rocke_value_t* v = rocke_b_global_load_vN(b, src, src_addr, ty, vec, vec * elem_bytes);
|
|
rocke_b_global_store_vN(b, dst, dst_addr, v, vec, vec * elem_bytes);
|
|
}
|
|
/* Python: for d in range(n_chunks * vec, H):
|
|
* s = load_scalar_as_f32(b, src, b.add(src_base, b.const_i32(d)),
|
|
* dtype=dtype)
|
|
* store_scalar_from_f32(b, dst, b.add(dst_base, b.const_i32(d)),
|
|
* s, dtype=dtype) */
|
|
for(d = n_chunks * vec; d < H; ++d)
|
|
{
|
|
rocke_value_t* s = rocke_b_load_scalar_as_f32(
|
|
b, src, rocke_b_add(b, src_base, rocke_b_const_i32(b, d)), dtype);
|
|
rocke_b_store_scalar_from_f32(
|
|
b, dst, rocke_b_add(b, dst_base, rocke_b_const_i32(b, d)), s, dtype);
|
|
}
|
|
}
|