0/10 已展开

LLM 分析

Scheduler 调度器:NVIDIA Olympus 启用 PE0 优先 SMT 兄弟线程

系列概况

  • 标题:[PATCH 0/2] sched: Enable preferred SMT siblings on NVIDIA Olympus
  • 作者:Andrea Righi <arighi@nvidia.com>
  • 版本:v1(本帖仅一个公开版本)
  • 规模:6 个文件,+178 / -9 行;2 个 patch
  • 修改文件
    • arch/arm64/include/asm/topology.h
    • arch/arm64/kernel/smp.c
    • arch/arm64/kernel/topology.c
    • kernel/sched/fair.c
    • kernel/sched/sched.h
    • kernel/sched/topology.c
  • 代码统计:arm64 拓扑部分 +64 行;通用调度器部分 +114/-9 行
  • Message-ID20260831181800.1668646-1-arighi@nvidia.com
  • 完整性:完整 — 含 0/2 cover letter、1/2 arm64 拓扑补丁、2/2 通用调度器补丁,以及 Christian Loehle、Dietmar Eggemann 的多轮 review 与 Andrea 的回复

补丁目的

NVIDIA Olympus 的两个 PE 在只有一方活跃时可以获得整个核心的资源(One-Thread模式),但 PE 切换会让核心进入 Two-Thread 模式并分割资源,且回到满资源模式需要兄弟线程在 WFI 停留 10 Ki cycles。补丁利用 SD_ASYM_PACKING 把 PE0 标为优选兄弟线程,并在通用 idle 选择路径中真正消费这个优先级,让可迁移负载始终落在 PE0,让 PE1 长期 idle,保持核心在单线程满资源模式。

旧流程的问题

  • idle CPU 选择只判断"是否 idle",不区分同一个 SMT core 内的 PE0/PE1
  • 唤醒时随机落到兄弟线程之一,活跃 PE 在两个 PE 间反复切换
  • 进入 Two-Thread 后 decode/issue/cache/TLB/vector 资源被瓜分
  • 兄弟线程重新进入 WFI 仍要等 10 Ki cycles 的资格窗口才能恢复满资源
  • NOHZ ILB 已避免不必要的兄弟唤醒(commit 293f9611ae735),但普通任务放置路径仍会选错
  • SD_ASYM_PACKING 本来能描述 SMT 兄弟优先级,但 idle 选择路径完全不读这个 flag

新流程

  1. smp_cpus_done() 调用 arm64_init_sched_topology()
  2. 通过 MIDR 检测 Olympus,置 olympus_prefer_pe0 = true
  3. 注册自定义 arm64_asym_smt_topologyarm64_smt_flags() 在标准 cpu_smt_flags() 上叠加 SD_ASYM_PACKING
  4. arch_asym_cpu_priority()MPIDR_Aff0 == 0 的 CPU 返回更高优先级
  5. build_sched_domains / sched_init_domains 扫描所有 SMT 域,发现 SD_ASYM_PACKING + SD_SHARE_CPUCAPACITY 即开启 sched_smt_asym_packing static key
  6. 所有 idle 选择路径(select_idle_core / select_idle_smt / select_idle_cpu / select_idle_capacity / select_idle_sibling)在返回前调用 __select_idle_smt_cpu(),在 SMT core 内选最高优先级的可用兄弟

Patch 概览

  • PATCH 1/2 arm64: topology: Prefer PE0 on NVIDIA Olympus SMT coresarch/arm64 三文件 +64 行,仅做 Olympus探测、注册自定义 topology、定义 arch_asym_cpu_priority()
  • PATCH 2/2 sched/fair: Honor asymmetric SMT priority in idle selectionkernel/sched/{fair.c,sched.h,topology.c} +114/-9 行,新增 sched_smt_asym_prefer / __select_idle_smt_cpu / sched_smt_asym_active(),并在8 个 idle 选择点全部改写

关键实现

/* Patch 1/2: arch/arm64/kernel/topology.c */
static bool olympus_prefer_pe0 __ro_after_init;

static int arm64_smt_flags(void)
{
    int flags = cpu_smt_flags();
    if (olympus_prefer_pe0)
        flags |= SD_ASYM_PACKING;
    return flags;
}

int arch_asym_cpu_priority(int cpu)
{
    if (!olympus_prefer_pe0)
        return 0;
    return MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 0) == 0;
}

void __init arm64_init_sched_topology(void)
{
    if (!IS_ENABLED(CONFIG_SCHED_SMT))            return;
    if ((read_cpuid_id() & MIDR_CPU_MODEL_MASK) != MIDR_NVIDIA_OLYMPUS) return;
    if (!topology_core_has_smt(smp_processor_id())) return;
    olympus_prefer_pe0 = true;
    set_sched_topology(arm64_asym_smt_topology);
 pr_info("Enabling PE0 SMT preference for NVIDIA Olympus\n");
}
/* Patch 2/2: kernel/sched/fair.c */
static bool sched_smt_asym_prefer(int cpu, int other)
{
    struct sched_domain *sd;
    for_each_domain(cpu, sd) {
        if (!(sd->flags & SD_SHARE_CPUCAPACITY)) break;
        if ((sd->flags & SD_ASYM_PACKING) &&
 cpumask_test_cpu(other, sched_domain_span(sd)))
            return sched_asym_prefer(cpu, other);
    }
    return false;
}

static int __select_idle_smt_cpu(struct task_struct *p, int cpu,
                                 const struct cpumask *cpus)
{
    int best = cpu, sibling;
    for_each_cpu_and(sibling, cpu_smt_mask(cpu), cpus) {
        if (sibling == best || !choose_idle_cpu(sibling, p)) continue;
        if (sched_smt_asym_prefer(sibling, best)) best = sibling;
    }
    return best;
}

类比

把 SMT 兄弟线程想象成一套两居室的合租房:单租客(一个 PE 忙)整套房随便用,第二个租客搬进来就瞬间变成上下铺(资源对半分);而上下铺一旦住过,即使第二个租客搬走,房子也得等 10 Ki cycles 才能恢复成"空一居"重新整租。调度器要做的是——总是把新租客安排到同一间卧室(PE0),避免每次搬家都把房子折腾成上下铺。SD_ASYM_PACKING 是"指定卧室号"的标签,idle 选择路径相当于"前台分房员",现在终于在分房前会看这个标签了。

Highlight:风险与注意点

  1. MIDR 探测只是过渡方案:依赖 MIDR_NVIDIA_OLYMPUS,缺少 firmware 描述 preferred SMT sibling 的接口,未来衍生型号会失效。
  2. PE 选择与 capacity 选择正交:用 SD_ASYM_PACKING + SD_ASYM_CPUCAPACITY 两个域描述不同语义,Christian 提议合并成 priority = is_primary ? 2*highest_perf : highest_perf,去掉 SD_ASYM_CPUCAPACITY;Andrea 表示会实验但担心丢掉 fitting/uclamp/misfit 语义。
  3. cpumask_test_cpu(other, sched_domain_span(sd)) 可能冗余:Dietmar 指出 other 通常已经在 SMT mask 里,需要确认是否仅用于排除跨核场景。
  4. 静态 priority 在 Two-Thread 模式下会误导 uclamp/fitting:双 PE 激活后每个 PE 的实际 capacity下降,但 fitting 仍按 capacity=1000 算,可能 over-commit。
  5. 可重入的 static_branch_inc_cpuslocked / _dec:依赖 cpus_read_lock(),多 CPU 并行构建/拆除拓扑时需确认锁持有者。
  6. PE0 不代表"更快":commit message 明确"preference does not identify a faster PE",下游用户若有"挑快 PE"假设需重新评估。

版本变化

本帖只发布 v1。讨论中 Andrea 接受 Christian 的合并 priority 思路并表态会做实验,但尚未提交 v2;Dietmar 在 patch 2/2 上提出 cpumask_test_cpu 冗余问题,也未在 v1 中修复,需要在后续版本回应。

一句话总结

NVIDIA Olympus 上 PE 切换会让核心反复陷入 Two-Thread 模式,Andrea 用 SD_ASYM_PACKING + 通用 idle 路径改造把可迁移任务钉在 PE0,让 PE1 长期 WFI,从而把88 线程 GEMM 吞吐从 9.4 拉到 10.1 TFLOP/s。

+--------------- SMT core on Olympus ---------------+
|  PE0 (Aff0=0) PE1 (Aff0=1)          |
|  priority=1                 priority=0            |
|  +-- picked first ----+ |
|  | v                          |
|  |  [__select_idle_smt_cpu] -> choose_idle_cpu()  |
|  | scan siblings, keep highest-priority idle |
|  +--------------------------------------------+ |
+--------------------------------------------------+

before:  task wakes -> idle sibling (random PE0/PE1) -> core toggles Two-Thread
after : task wakes -> idle sibling, but PE0 wins -> core stays One-Thread