many versions of simdid

This commit is contained in:
Youmu Liu
2026-02-25 18:02:56 +08:00
parent b71515b2d4
commit 1b04196314
6 changed files with 188 additions and 51 deletions
+19 -4
View File
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="MatrixParallel"
// Project: StgSharp
// AuthorGroup: Nitload Space
// Copyright (c) Nitload Space. All rights reserved.
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -57,7 +57,9 @@ namespace StgSharp.Mathematics.Numeric
private static bool IsInitialized { get; set; } = false;
internal static unsafe IntrinsicContext* GetContext(SIMDID simd)
internal static unsafe IntrinsicContext* GetContext(
SIMDID simd
)
{
if (!IsParallelSupported) {
return null;
@@ -92,7 +94,10 @@ namespace StgSharp.Mathematics.Numeric
{
MatrixParallelThread thread = new MatrixParallelThread(i);
_threads.Push(thread);
SpinWait.SpinUntil(() => thread.State == MatrixThreadState.Ready);
}
// TODO 构建全局的保底上下文
if (contextMap.Keys.Count == 1)
{
for (int i = 0; i < 4; i++)
@@ -110,7 +115,17 @@ namespace StgSharp.Mathematics.Numeric
[LibraryImport(Native.LibName, EntryPoint = "load_intrinsic_function")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe partial void LoadIntrinsicFunction(IntrinsicContext* context, ulong id);
private static unsafe partial ulong CompareSIMDID(
IntrinsicContext* context,
ulong id
);
[LibraryImport(Native.LibName, EntryPoint = "load_intrinsic_function")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe partial void LoadIntrinsicFunction(
IntrinsicContext* context,
ulong id
);
}
}
@@ -2,8 +2,8 @@
// -----------------------------------------------------------------------
// file="MatrixParallelThread"
// Project: StgSharp
// AuthorGroup: Nitload Space
// Copyright (c) Nitload Space. All rights reserved.
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -35,9 +35,11 @@ namespace StgSharp.Mathematics.Numeric
internal enum MatrixThreadState : int
{
Ready = 0,
Running = 1,
Idle = 2,
Born = 0,
Ready = 1,
Running = 2,
Idle = 4,
Retired = 8,
}
@@ -45,11 +47,13 @@ namespace StgSharp.Mathematics.Numeric
{
private IntPtr* _context;
private bool disposedValue;
private volatile bool _disposed, _isRetireRequested = false;
private readonly ManualResetEventSlim _resetEvent;
private readonly Thread _managedThread;
internal MatrixParallelThread(int poolId)
internal MatrixParallelThread(
int poolId
)
{
_resetEvent = new ManualResetEventSlim(false);
_managedThread = new Thread(MatrixParallelWorkLoad);
@@ -69,7 +73,7 @@ namespace StgSharp.Mathematics.Numeric
internal MatrixParallelWrap BindedWrap { get; set; }
internal MatrixThreadState state { get; set; }
internal MatrixThreadState State { get; set; }
internal SIMDID Simd { get; set; }
@@ -80,7 +84,9 @@ namespace StgSharp.Mathematics.Numeric
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void ExecuteTaskPackage(MatrixParallelQueue taskQueue)
public static unsafe void ExecuteTaskPackage(
MatrixParallelQueue taskQueue
)
{
MatrixParallelTaskPackage* p = taskQueue.Package;
switch (p->ComputeMode.OperationStyle)
@@ -121,44 +127,54 @@ namespace StgSharp.Mathematics.Numeric
_resetEvent.Set();
}
[DllImport(
dllName: Native.LibName,
EntryPoint = "sn_get_simd_level_local",
CallingConvention = CallingConvention.Cdecl)]
internal static extern ulong GetSimdId();
[LibraryImport(Native.LibName, EntryPoint = "sn_get_simd_level_local")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial ulong GetSimdId();
[DllImport(
dllName:Native.LibName,
EntryPoint = "sn_set_thread_affinity",
CallingConvention = CallingConvention.Cdecl)]
internal static extern void SetThreadAffinity(int coreId);
[LibraryImport(Native.LibName, EntryPoint = "sn_set_thread_affinity")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial void SetThreadAffinity(
int coreId
);
protected virtual void Dispose(bool disposing)
protected virtual void Dispose(
bool disposing
)
{
if (!disposedValue)
if (!_disposed)
{
if (disposing)
{
_resetEvent.Dispose();
_isRetireRequested = true;
_resetEvent.Set();
_managedThread.Join();
_resetEvent.Dispose();
}
disposedValue = true;
_disposed = true;
}
}
private unsafe void MatrixParallelWorkLoad()
{
State = MatrixThreadState.Born;
// Bind OS-thread affinity once when the worker starts running
SetThreadAffinity(PoolId);
Thread.Sleep(1);
Thread.Yield();
SIMDID feature = new();
feature.Mask = GetSimdId();
_context = (nint*)MatrixParallel.GetContext(feature);
State = MatrixThreadState.Ready;
while (true)
{
// Thread self = Thread.CurrentThread;
_resetEvent.Wait();
_resetEvent.Reset();
if (_isRetireRequested)
{
break;
}
State = MatrixThreadState.Running;
AfterWork = BindedWrap.AfterWork;
_managedThread.Priority = ThreadPriority.Highest;
if (IsSingle)
@@ -170,7 +186,9 @@ namespace StgSharp.Mathematics.Numeric
BindedWrap.ReportThreadAccomplished();
}
_managedThread.Priority = AfterWork;
State = MatrixThreadState.Idle;
}
State = MatrixThreadState.Retired;
}
}
+40 -14
View File
@@ -134,14 +134,15 @@ AVX Feature (bits 47..40)
| bit | 47 | 46 | 45 | 44 | 43 | 42 | 41 | 40 |
|-----|----|----|----|----|----|----|----|----|
| use | R | R | R | R | F16C | FMA | AVX2 | AVX |
| use | R | R | R | DUAL | F16C | FMA | AVX2 | AVX |
Rules:
- **AVX (bit 40)**: AVX present.
- **AVX2 (bit 41)**: AVX2 present. For the AVX feature byte to be considered valid, both AVX and AVX2 must be set.
- **FMA (bit 42)**: FMA present.
- **F16C (bit 43)**: F16C present.
- **[44..47]**: Reserved for other AVX-family extensions (e.g., BMI/BMI2, IFMA, XOP) as needed.
- **DUAL (bit 44)**: YMM execution is **glued/split-lane** (two fused 128-bit units rather than a native 256-bit path). Detection: AMD Zen1/Zen+ (Family 0x17, model < 0x30), Hygon (all, Zen1-based), VIA/Zhaoxin (all). When set, code using YMM operations may incur split-register overhead; prefer 128-bit SSE paths on these targets.
- **[45..47]**: Reserved for other AVX-family extensions (e.g., IFMA, XOP) as needed.
---
@@ -207,17 +208,36 @@ When `MainLevel < AMX`, this byte can be all zeros.
## 11) uArchHi ([63..56], 8 bits)
Purpose: optional high-byte strategy field to address issues like early Intel/AMD "glue" AVX512 that are not expressible purely via CPUID subsets. Defaults to 0.
Purpose: **bitmask** byte encoding core topology classification and SIMD execution-width policy for the detected core. All bits default to 0.
Suggested minimal use:
- `0`: unused
- `1`: ForceDual (treat as AVX512-DUAL even if impl is unknown)
- `2`: ForceNative (treat as AVX512-NATIVE even if impl is unknown)
- `3..`: Implementation-maintained uArch enumeration (e.g., SKX/ICL/SPR/ZEN4/ZEN5…)
uArchHi bitmask layout:
Practical strategy for a single maintainer:
- AMD: detection side sets `AVX512_IMPL = DUAL` directly (no uArch needed)
- Intel: enumerate only selected models of interest; otherwise Unknown follows default policy
| bit (relative to [63..56]) | Name | Note |
|---:|------|------|
| 0 | HYBRID_E | E-core (efficiency/small core) in a hybrid-topology processor (e.g., Intel Alder Lake+ E-core) |
| 1 | HYBRID_P | P-core (performance/big core) in a hybrid-topology processor (e.g., Intel Alder Lake+ P-core) |
| 2 | SIMD_DUAL | SIMD execution is glued/dual (not full-width). Summary flag: set when `AVX_DUAL=1` **or** `AVX512_IMPL=DUAL`. |
| 3..7 | Reserved | |
Core topology encoding (bits [1..0], numerically comparable — higher = stronger):
| bits [1..0] | Value | Meaning |
|:-----------:|:-----:|----------|
| 00 | 0 | Unknown — undetected hybrid type or pre-CPUID.1A hardware |
| 01 | 1 | **Hybrid-E** — efficiency/small core (a3) |
| 10 | 2 | **Hybrid-P** — performance/big core (a2) |
| 11 | 3 | **Unified** — confirmed non-hybrid; full-topology core (a1) |
The 2-bit field is directly comparable as an unsigned integer: `unified(3) > P-core(2) > E-core(1) > unknown(0)`.
Detection:
- **Non-hybrid (unified)**: if `CPUID.7.0 EDX[15]` (HYBRID) is **not** set, the CPU is confirmed non-hybrid → set both `HYBRID_P` and `HYBRID_E` (topology = 11).
- **Hybrid P-core**: HYBRID flag set and `CPUID.0x1A.0 EAX[31:24] == 0x40` → set only `HYBRID_P` (topology = 10).
- **Hybrid E-core**: HYBRID flag set and `CPUID.0x1A.0 EAX[31:24] == 0x20` → set only `HYBRID_E` (topology = 01).
- **Unknown hybrid** (flag set but core type unrecognised): leave topology at 00.
- **SIMD_DUAL**: set when any of the following: `AVX Feature DUAL=1` (glued YMM), or `AVX512_IMPL=DUAL`.
`sn_get_unite_simd` computes topology bits [1..0] with bitwise **AND** (conservative intersection — weaker topology wins) and SIMD_DUAL with bitwise **OR** (dual if either core is dual). `sn_compare_simd` compares topology value numerically as step 6 and SIMD_DUAL as the final (step 7) tie-break.
---
@@ -232,7 +252,7 @@ Practical strategy for a single maintainer:
- Intel: Decide DUAL/NATIVE based on family/model or policy table; if uncertain, use Unknown
- When VNNI/BF16/FP16/VBMI/VBMI2 are detected, set the corresponding bits.
4) If `MainLevel >= AMX`: fill the AMX byte.
5) When AVX/AVX2 are present, populate the AVX Feature byte: set AVX and AVX2; set FMA and F16C when detected; leave reserved bits for future extensions.
5) When AVX/AVX2 are present, populate the AVX Feature byte: set `AVX` and `AVX2`; set `FMA` and `F16C` when detected; set `DUAL` when the YMM execution path is split/glued (AMD Zen1/Zen+ Family 0x17 model < 0x30, Hygon all models, VIA/Zhaoxin all models).
6) **AVX10 detection** (requires `CPUID leaf 7 subleaf 1, EDX[19] == 1` as the AVX10 convergence bit):
- If AVX10 is indicated, query `CPUID leaf 0x24, subleaf 0`:
- Read `EBX[7:0]` for version number; set `AVX10`, `V1`, and optionally `V2`.
@@ -240,10 +260,16 @@ Practical strategy for a single maintainer:
- Read `EBX[18]` for 512-bit support → set `512W`.
- Promote `MainLevel` to `AVX10` (value 7).
- The `AVX512 Feature` field should still be filled for backward compatibility.
7) Use uArchHi only when additional policy is needed; otherwise leave as 0.
7) Populate uArchHi:
- If `CPUID.7.0 EDX[15]` (HYBRID) **not** set: set `HYBRID_P | HYBRID_E` (unified = 11).
- If HYBRID is set, read `CPUID.0x1A.0 EAX[31:24]`:
- `0x40` (P-core) → set only `HYBRID_P` (topology = 10).
- `0x20` (E-core) → set only `HYBRID_E` (topology = 01).
- Otherwise leave topology at 00 (unknown hybrid).
- If `AVX Feature DUAL=1` or `AVX512_IMPL=DUAL`, set `SIMD_DUAL`.
---
## 13) Choice for High Bits Reservation
`[31..24]` is reserved for AVX512 extensions/AI. `[47..44]` is reserved for AVX-family extensions beside the AVX byte. `[55..53]` is reserved for future AVX10 versions. uArchHi `[63..56]` remains available for minimal policy hints. If IFMA or other extensions are more important, repurpose the reserved bits while keeping `BASE`, `Impl`, and `AVX10` core bits unchanged.
`[31..24]` is reserved for AVX512 extensions/AI. `[47..45]` is reserved for AVX-family extensions (bit 44 is now `DUAL`). `[55..53]` is reserved for future AVX10 versions. uArchHi `[63..56]` bits [3..7] are reserved; bits [0..2] encode hybrid topology and SIMD-dual policy.
@@ -75,6 +75,7 @@
#define SIMDID_AVX2 (1U << 1)
#define SIMDID_AVX_FMA (1U << 2)
#define SIMDID_AVX_F16C (1U << 3)
#define SIMDID_AVX_DUAL (1U << 4) /* glued/split-lane YMM (two 128-bit units; Zen1, Hygon, VIA/Zhaoxin) */
#endif
#ifndef SIMDID_AMX_TILE
@@ -91,6 +92,19 @@
#define SIMDID_AVX10_512W (1U << 4)
#endif
/* uArchHi ([63..56]) bitmask — core topology and SIMD-width execution policy */
#ifndef SIMDID_UARCH_HYBRID_P
/* Topology 2-bit field (bits [1..0] of uArchHi byte):
* 00 = unknown (undetected hybrid type or pre-CPUID.1A hardware)
* 01 = E-core (efficiency/small core in a hybrid CPU)
* 10 = P-core (performance/big core in a hybrid CPU)
* 11 = unified (confirmed non-hybrid; numerically strongest)
* Higher numeric value == stronger / higher-capability topology. */
#define SIMDID_UARCH_HYBRID_E (1U << 0) /* E-core (efficiency/small) in a hybrid topology */
#define SIMDID_UARCH_HYBRID_P (1U << 1) /* P-core (performance/big) in a hybrid topology */
#define SIMDID_UARCH_SIMD_DUAL (1U << 2) /* SIMD is glued/dual (summary: AVX_DUAL or AVX512_IMPL=DUAL) */
#endif
// Pack helpers (define locally to avoid missing macros when included standalone)
#ifndef SIMDID_PACK_ROOT
#define SIMDID_PACK_ROOT(root) (((uint64_t)(root) & 0xFULL) << SIMDID_SHIFT_ROOT)
+49 -1
View File
@@ -77,6 +77,25 @@ static int is_intel_native_avx512(int family, int model)
}
}
/* Returns 1 if this AMD/Hygon CPU uses split-lane YMM (two fused 128-bit execution units).
* - AMD Zen1 (family 0x17, model < 0x30) and Zen+ (0x18-0x2F) use a split/glued path.
* - Hygon Dhyana is architecturally Zen1-based; always split.
* - AMD Zen2+ (family 0x17 model >= 0x30, family 0x19+) have native 256-bit execution. */
static int is_amd_glue_ymm(const char *vendor, int cpuid1_eax)
{
if (strcmp(vendor, "HygonGenuine") == 0)
return 1; /* Hygon is Zen1-based; always split YMM */
if (strcmp(vendor, "AuthenticAMD") != 0)
return 0;
int base_fam = (cpuid1_eax >> 8) & 0xF;
int ext_fam = (cpuid1_eax >> 20) & 0xFF;
int disp_fam = (base_fam < 0xF) ? base_fam : (base_fam + ext_fam);
int base_mdl = (cpuid1_eax >> 4) & 0xF;
int ext_mdl = (cpuid1_eax >> 16) & 0xF;
int disp_mdl = (ext_mdl << 4) | base_mdl;
return (disp_fam == 0x17) && (disp_mdl < 0x30); /* Zen1/Zen+ */
}
SN_API SIMDID SN_DECL sn_get_simd_level_local()
{
SIMDID _simdid = { 0 };
@@ -93,9 +112,12 @@ SN_API SIMDID SN_DECL sn_get_simd_level_local()
const int is_intel = (strcmp(vendor, "GenuineIntel") == 0);
const int is_amd = (strcmp(vendor, "AuthenticAMD") == 0) ||
(strcmp(vendor, "HygonGenuine") == 0);
const int is_via = (strcmp(vendor, "CentaurHauls") == 0) ||
(memcmp(vendor, " Shanghai ", 12) == 0); /* VIA / Zhaoxin */
/* CPUID.1 */
get_cpuid(1, 0, &eax, &ebx, &ecx, &edx);
int cpuid1_eax = eax; /* saved for Zen1/Zen+ glue-YMM detection */
int family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF0);
int model = ((eax >> 4) & 0xF) | ((eax >> 12) & 0xF0);
@@ -108,12 +130,14 @@ SN_API SIMDID SN_DECL sn_get_simd_level_local()
uint64_t root = SIMDID_PACK_ROOT(SIMDID_ROOT_X86_64);
uint64_t manu = SIMDID_PACK_MANU(is_intel ? SIMDID_MANU_INTEL :
is_amd ? SIMDID_MANU_AMD :
is_via ? SIMDID_MANU_VIA :
SIMDID_MANU_UNKNOWN);
uint64_t main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_NONE);
uint64_t avx512_bits = 0;
uint64_t amx_bits = 0;
uint64_t avx_bits = 0;
uint64_t avx10_bits = 0;
int has_hybrid = 0; /* filled from CPUID.7.0 EDX[15] when AVX present */
/* Baseline main level */
if (has_sse42)
@@ -122,6 +146,10 @@ SN_API SIMDID SN_DECL sn_get_simd_level_local()
if (has_avx && has_osxsave && xgetbv_has_xmm_ymm()) {
avx_bits |= SIMDID_AVX;
/* Glued/split-lane YMM: early AMD Zen1/Zen+, Hygon, and VIA/Zhaoxin */
if (is_amd_glue_ymm(vendor, cpuid1_eax) || is_via)
avx_bits |= SIMDID_AVX_DUAL;
/* CPUID.7.0 */
get_cpuid(7, 0, &eax, &ebx, &ecx, &edx);
const int has_bmi1 = (ebx & (1 << 3)) != 0;
@@ -140,6 +168,7 @@ SN_API SIMDID SN_DECL sn_get_simd_level_local()
const int has_amx_tile = (edx & (1 << 24)) != 0;
const int has_amx_int8 = (edx & (1 << 25)) != 0;
const int has_amx_bf16 = (edx & (1 << 22)) != 0;
has_hybrid = (edx & (1 << 15)) != 0; /* Intel hybrid core topology */
/* leaf 7, subleaf 1 for BF16/FP16/AVX10 convergence */
get_cpuid(7, 1, &eax, &ebx, &ecx, &edx);
@@ -217,9 +246,28 @@ SN_API SIMDID SN_DECL sn_get_simd_level_local()
}
}
/* uArchHi: hybrid core topology and SIMD-dual summary */
uint64_t uarch_bits = 0;
if (has_hybrid) {
int h_eax = 0, h_ebx = 0, h_ecx = 0, h_edx = 0;
get_cpuid(0x1A, 0, &h_eax, &h_ebx, &h_ecx, &h_edx);
unsigned core_type = (unsigned)(h_eax >> 24) & 0xFFU;
if (core_type == 0x40) /* Intel Core / P-core → 10 */
uarch_bits |= SIMDID_UARCH_HYBRID_P;
else if (core_type == 0x20) /* Intel Atom / E-core → 01 */
uarch_bits |= SIMDID_UARCH_HYBRID_E;
/* else unknown hybrid core type → topology stays 00 */
} else {
/* Confirmed non-hybrid (unified) → set both bits = 11 (numerically strongest) */
uarch_bits |= SIMDID_UARCH_HYBRID_P | SIMDID_UARCH_HYBRID_E;
}
if ((avx_bits & SIMDID_AVX_DUAL) ||
((avx512_bits & SIMDID_AVX512_IMPL_MASK) == SIMDID_AVX512_IMPL_DUAL))
uarch_bits |= SIMDID_UARCH_SIMD_DUAL;
_simdid.mask = root | manu | main_lvl | SIMDID_PACK_AVX512(avx512_bits) |
SIMDID_PACK_AMX(amx_bits) | SIMDID_PACK_AVX(avx_bits) |
SIMDID_PACK_AVX10(avx10_bits);
SIMDID_PACK_AVX10(avx10_bits) | SIMDID_PACK_UARCH(uarch_bits);
return _simdid;
}
+24 -8
View File
@@ -61,7 +61,7 @@ static int impl_strength(uint16_t avx512)
* - AVX features : bitwise AND
* - AVX10 features : bitwise AND
* - Root / Manu : keep left (must be same arch in practice)
* - uArchHi : min (most conservative policy)
* - uArchHi : bitwise OR (HYBRID_P/E union; SIMD_DUAL set if either core is dual)
* ----------------------------------------------------------------------- */
SN_API SIMDID SN_DECL sn_get_unite_simd(SIMDID left, SIMDID right)
{
@@ -102,10 +102,14 @@ SN_API SIMDID SN_DECL sn_get_unite_simd(SIMDID left, SIMDID right)
/* AVX10 (byte 6): AND */
out.make_byte[6] = left.make_byte[6] & right.make_byte[6];
/* uArchHi (byte 7): min */
/* uArchHi (byte 7):
* topology bits [1..0]: AND — conservative intersection (weaker topology wins)
* SIMD_DUAL bit [2]: OR — dual if either core is dual */
unsigned char lu = FIELD_UARCH(left);
unsigned char ru = FIELD_UARCH(right);
out.make_byte[7] = (char)(lu < ru ? lu : ru);
unsigned char topo = (lu & 0x3u) & (ru & 0x3u);
unsigned char dual = (lu | ru) & (unsigned char)SIMDID_UARCH_SIMD_DUAL;
out.make_byte[7] = (char)(topo | dual);
return out;
}
@@ -142,8 +146,12 @@ SN_API SIMDID SN_DECL sn_get_unite_simd(SIMDID left, SIMDID right)
* DUAL(weakest) < UNKNOWN < NATIVE(strongest).
* This is not ISA capability but execution-width risk.
*
* 6. uArchHi (final tie-break)
* Higher value = more specific / stronger policy hint.
* 6. uArchHi topology bits [1..0]
* Numeric value: unified(11=3) > P-core(10=2) > E-core(01=1) > unknown(00=0).
* Higher value = stronger / higher-capability core form.
*
* 7. uArchHi SIMD_DUAL (final tie-break)
* Non-dual (SIMD_DUAL=0) > dual (SIMD_DUAL=1).
* ----------------------------------------------------------------------- */
SN_API int SN_DECL sn_compare_simd(SIMDID left, SIMDID right)
{
@@ -186,11 +194,19 @@ SN_API int SN_DECL sn_compare_simd(SIMDID left, SIMDID right)
return ls > rs ? 1 : -1;
}
/* 6. uArchHi (final tie-break) */
/* 6. uArchHi topology bits [1..0]: unified(3) > P(2) > E(1) > unknown(0) */
unsigned lu = FIELD_UARCH(left);
unsigned ru = FIELD_UARCH(right);
if (lu != ru)
return lu > ru ? 1 : -1;
unsigned lt = lu & 0x3u;
unsigned rt = ru & 0x3u;
if (lt != rt)
return lt > rt ? 1 : -1;
/* 7. uArchHi SIMD_DUAL: non-dual (SIMD_DUAL=0) > dual (SIMD_DUAL=1) */
int ld = (lu & SIMDID_UARCH_SIMD_DUAL) != 0;
int rd = (ru & SIMDID_UARCH_SIMD_DUAL) != 0;
if (ld != rd)
return ld ? -1 : 1; /* non-dual is stronger */
return 0;
}