Skip to content

Commit 269e1cd

Browse files
committed
RISC-V: add get_L2_size() and blas_set_parameter() for cache-aware GEMM blocking
RISC-V was the only major architecture without a get_L2_size() / blas_set_parameter() implementation, so the GEMM cache-blocking parameters (P/Q/R) were fixed at compile time regardless of the actual L2 cache size. Because the blocking is now derived from the L2 cache detected at runtime rather than a fixed compile-time constant, future RISC-V cores - which are arriving with progressively larger and more varied L2 caches - get more optimal blocking automatically, and the port gains the same runtime-tuning hook x86 and LoongArch already use. This adds, under ARCH_RISCV64: - get_L2_size(): reads the level-2 (unified) cache size from Linux sysfs (/sys/devices/system/cpu/cpu0/cache/index*/{level,size}); RISC-V has no architectural cache-size query like x86 CPUID or LoongArch CPUCFG. Falls back to 512 KB when sysfs is unavailable. - blas_set_parameter(): scales each precision's packed-A dimension P from the detected L2. The base blocking and the reference cache size come from the active core's own param.h block (*_DEFAULT_P_BASE, RISCV_L2_REFERENCE_KB), so the function carries no core-specific constants and is a no-op for cores that do not opt in. Q and R keep their param.h defaults. - driver/others/memory.c and common_macro.h: add ARCH_RISCV64 to the existing architecture lists that call blas_set_parameter() and declare the runtime parameter variables (sgemm_p, dgemm_p, ...). - param.h RISCV64_ZVL256B: declares the per-core base blocking + reference and maps SGEMM/DGEMM/CGEMM/ZGEMM DEFAULT_P to the runtime variables for static builds; DYNAMIC_ARCH keeps the literals, since kernel/setparam-ref.c init_parameter() initialises the gotoblas table from these macros and blas_set_parameter() is not called on the dynamic path. Only RISCV64_ZVL256B opts in so far; its base + reference are tuned on the SpaceMiT X60, where a 512 KB L2 reproduces the stock blocking, so this is performance-neutral on current hardware. Verified: a static RISCV64_ZVL256B build reproduces the stock 128/128/16384 (SGEMM) and 64/128/8192 (DGEMM) blocking; a DYNAMIC_ARCH build compiles cleanly (per-core setparam-ref objects build without error); and get_L2_size() reads the correct size on both a SpaceMiT X60 (512 KB L2) and a SiFive U74 / VisionFive 2 (2 MB L2).
1 parent 992a536 commit 269e1cd

4 files changed

Lines changed: 74 additions & 7 deletions

File tree

common_macro.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2712,7 +2712,7 @@
27122712
#ifndef ASSEMBLER
27132713
#if !defined(DYNAMIC_ARCH) \
27142714
&& (defined(ARCH_X86) || defined(ARCH_X86_64) || defined(ARCH_IA64) || defined(ARCH_MIPS64) || defined(ARCH_ARM64) \
2715-
|| defined(ARCH_LOONGARCH64) || defined(ARCH_E2K) || defined(ARCH_ALPHA))
2715+
|| defined(ARCH_LOONGARCH64) || defined(ARCH_E2K) || defined(ARCH_ALPHA) || defined(ARCH_RISCV64))
27162716
extern BLASLONG gemm_offset_a;
27172717
extern BLASLONG gemm_offset_b;
27182718
extern BLASLONG bgemm_p;

driver/others/memory.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,7 +1220,7 @@ UNLOCK_COMMAND(&alloc_lock);
12201220
if (!blas_num_threads) blas_cpu_number = blas_get_cpu_number();
12211221
#endif
12221222

1223-
#if defined(ARCH_X86) || defined(ARCH_X86_64) || defined(ARCH_IA64) || defined(ARCH_MIPS64) || defined(ARCH_ARM64) || defined(ARCH_LOONGARCH64)
1223+
#if defined(ARCH_X86) || defined(ARCH_X86_64) || defined(ARCH_IA64) || defined(ARCH_MIPS64) || defined(ARCH_ARM64) || defined(ARCH_LOONGARCH64) || defined(ARCH_RISCV64)
12241224
#ifndef DYNAMIC_ARCH
12251225
blas_set_parameter();
12261226
#endif
@@ -2822,7 +2822,7 @@ void *blas_memory_alloc(int procpos){
28222822
if (!blas_num_threads) blas_cpu_number = blas_get_cpu_number();
28232823
#endif
28242824

2825-
#if defined(ARCH_X86) || defined(ARCH_X86_64) || defined(ARCH_IA64) || defined(ARCH_MIPS64) || defined(ARCH_ARM64) || defined(ARCH_LOONGARCH64)
2825+
#if defined(ARCH_X86) || defined(ARCH_X86_64) || defined(ARCH_IA64) || defined(ARCH_MIPS64) || defined(ARCH_ARM64) || defined(ARCH_LOONGARCH64) || defined(ARCH_RISCV64)
28262826
#ifndef DYNAMIC_ARCH
28272827
blas_set_parameter();
28282828
#endif

driver/others/parameter.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,3 +902,53 @@ void blas_set_parameter(void)
902902
}
903903

904904
#endif
905+
906+
#if defined(ARCH_RISCV64)
907+
908+
#include <stdio.h>
909+
910+
/* RISC-V has no architectural cache-size query (cf. x86 CPUID / LoongArch
911+
CPUCFG), so read the L2 (level 2, unified) size from Linux sysfs and fall
912+
back to 512 KB. Returns the L2 size in kilobytes. */
913+
int get_L2_size(void) {
914+
int size = 0;
915+
#if defined(OS_LINUX) || defined(OS_ANDROID)
916+
int idx;
917+
for (idx = 0; idx <= 4; idx++) {
918+
char path[80]; FILE *fp; int level = 0; long val = 0; char unit = 0;
919+
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/level", idx);
920+
fp = fopen(path, "r"); if (fp == NULL) continue;
921+
if (fscanf(fp, "%d", &level) != 1) { fclose(fp); continue; }
922+
fclose(fp); if (level != 2) continue;
923+
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/size", idx);
924+
fp = fopen(path, "r"); if (fp == NULL) continue;
925+
if (fscanf(fp, "%ld%c", &val, &unit) >= 1) {
926+
if (unit == 'M' || unit == 'm') val *= 1024;
927+
if (unit == 'G' || unit == 'g') val *= 1024 * 1024;
928+
size = (int)val;
929+
}
930+
fclose(fp); if (size > 0) break;
931+
}
932+
#endif
933+
if (size <= 0) size = 512;
934+
return size;
935+
}
936+
937+
void blas_set_parameter(void) {
938+
#if defined(SGEMM_DEFAULT_P_BASE)
939+
/* Scale each precision's packed-A dimension P from the detected L2, relative
940+
to the size the active core's base blocking targets (RISCV_L2_REFERENCE_KB).
941+
The bases come from the core's own param.h block, so this is not tied to any
942+
single core; Q and R keep their param.h defaults. */
943+
int l2 = get_L2_size(); /* KB */
944+
int scale = l2 / RISCV_L2_REFERENCE_KB;
945+
if (scale < 1) scale = 1;
946+
if (scale > 4) scale = 4;
947+
sgemm_p = SGEMM_DEFAULT_P_BASE * scale;
948+
dgemm_p = DGEMM_DEFAULT_P_BASE * scale;
949+
cgemm_p = CGEMM_DEFAULT_P_BASE * scale;
950+
zgemm_p = ZGEMM_DEFAULT_P_BASE * scale;
951+
#endif
952+
}
953+
954+
#endif

param.h

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3265,10 +3265,27 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32653265
#define SHGEMM_DEFAULT_P 128
32663266
#undef SBGEMM_DEFAULT_P
32673267
#define SBGEMM_DEFAULT_P 128
3268-
#define SGEMM_DEFAULT_P 128
3269-
#define DGEMM_DEFAULT_P 64
3270-
#define CGEMM_DEFAULT_P 64
3271-
#define ZGEMM_DEFAULT_P 64
3268+
/* Base packed-A (P) blocking for this core. On static builds blas_set_parameter()
3269+
scales P from the L2 cache detected at runtime, relative to RISCV_L2_REFERENCE_KB
3270+
(the L2 size these bases target); Q and R keep their param.h defaults. A cache
3271+
equal to the reference reproduces the stock blocking. DYNAMIC_ARCH uses the
3272+
literals directly (kernel/setparam-ref.c fills the gotoblas table from them). */
3273+
#define RISCV_L2_REFERENCE_KB 512
3274+
#define SGEMM_DEFAULT_P_BASE 128
3275+
#define DGEMM_DEFAULT_P_BASE 64
3276+
#define CGEMM_DEFAULT_P_BASE 64
3277+
#define ZGEMM_DEFAULT_P_BASE 64
3278+
#if defined(DYNAMIC_ARCH)
3279+
#define SGEMM_DEFAULT_P SGEMM_DEFAULT_P_BASE
3280+
#define DGEMM_DEFAULT_P DGEMM_DEFAULT_P_BASE
3281+
#define CGEMM_DEFAULT_P CGEMM_DEFAULT_P_BASE
3282+
#define ZGEMM_DEFAULT_P ZGEMM_DEFAULT_P_BASE
3283+
#else
3284+
#define SGEMM_DEFAULT_P sgemm_p
3285+
#define DGEMM_DEFAULT_P dgemm_p
3286+
#define CGEMM_DEFAULT_P cgemm_p
3287+
#define ZGEMM_DEFAULT_P zgemm_p
3288+
#endif
32723289

32733290
#undef SHGEMM_DEFAULT_Q
32743291
#define SHGEMM_DEFAULT_Q 128

0 commit comments

Comments
 (0)