A memory tiering abstract distance calculation algorithm based on ACPI
HMAT is implemented. The basic idea is as follows.
The performance attributes of system default DRAM nodes are recorded
as the base line. Whose abstract distance is MEMTIER_ADISTANCE_DRAM.
Then, the ratio of the abstract distance of a memory node (target) to
MEMTIER_ADISTANCE_DRAM is scaled based on the ratio of the performance
attributes of the node to that of the default DRAM nodes.
Signed-off-by: "Huang, Ying" <[email protected]>
Cc: Aneesh Kumar K.V <[email protected]>
Cc: Wei Xu <[email protected]>
Cc: Alistair Popple <[email protected]>
Cc: Dan Williams <[email protected]>
Cc: Dave Hansen <[email protected]>
Cc: Davidlohr Bueso <[email protected]>
Cc: Johannes Weiner <[email protected]>
Cc: Jonathan Cameron <[email protected]>
Cc: Michal Hocko <[email protected]>
Cc: Yang Shi <[email protected]>
Cc: Rafael J Wysocki <[email protected]>
---
drivers/acpi/numa/hmat.c | 138 ++++++++++++++++++++++++++++++++++-
include/linux/memory-tiers.h | 2 +
mm/memory-tiers.c | 2 +-
3 files changed, 140 insertions(+), 2 deletions(-)
diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c
index 2dee0098f1a9..306a912090f0 100644
--- a/drivers/acpi/numa/hmat.c
+++ b/drivers/acpi/numa/hmat.c
@@ -24,6 +24,7 @@
#include <linux/node.h>
#include <linux/sysfs.h>
#include <linux/dax.h>
+#include <linux/memory-tiers.h>
static u8 hmat_revision;
static int hmat_disable __initdata;
@@ -759,6 +760,137 @@ static int hmat_callback(struct notifier_block *self,
return NOTIFY_OK;
}
+static int hmat_adistance_disabled;
+static struct node_hmem_attrs default_dram_attrs;
+
+static void dump_hmem_attrs(struct node_hmem_attrs *attrs)
+{
+ pr_cont("read_latency: %u, write_latency: %u, read_bandwidth: %u, write_bandwidth: %u\n",
+ attrs->read_latency, attrs->write_latency,
+ attrs->read_bandwidth, attrs->write_bandwidth);
+}
+
+static void disable_hmat_adistance_algorithm(void)
+{
+ hmat_adistance_disabled = true;
+}
+
+static int hmat_init_default_dram_attrs(void)
+{
+ struct memory_target *target;
+ struct node_hmem_attrs *attrs;
+ int nid, pxm;
+ int nid_dram = NUMA_NO_NODE;
+
+ if (default_dram_attrs.read_latency +
+ default_dram_attrs.write_latency != 0)
+ return 0;
+
+ if (!default_dram_type)
+ return -EIO;
+
+ for_each_node_mask(nid, default_dram_type->nodes) {
+ pxm = node_to_pxm(nid);
+ target = find_mem_target(pxm);
+ if (!target)
+ continue;
+ attrs = &target->hmem_attrs[1];
+ if (nid_dram == NUMA_NO_NODE) {
+ if (attrs->read_latency + attrs->write_latency == 0 ||
+ attrs->read_bandwidth + attrs->write_bandwidth == 0) {
+ pr_info("hmat: invalid hmem attrs for default DRAM node: %d,\n",
+ nid);
+ pr_info(" ");
+ dump_hmem_attrs(attrs);
+ pr_info(" disable hmat based abstract distance algorithm.\n");
+ disable_hmat_adistance_algorithm();
+ return -EIO;
+ }
+ nid_dram = nid;
+ default_dram_attrs = *attrs;
+ continue;
+ }
+
+ /*
+ * The performance of all default DRAM nodes is expected
+ * to be same (that is, the variation is less than 10%).
+ * And it will be used as base to calculate the abstract
+ * distance of other memory nodes.
+ */
+ if (abs(attrs->read_latency - default_dram_attrs.read_latency) * 10 >
+ default_dram_attrs.read_latency ||
+ abs(attrs->write_latency - default_dram_attrs.write_latency) * 10 >
+ default_dram_attrs.write_latency ||
+ abs(attrs->read_bandwidth - default_dram_attrs.read_bandwidth) * 10 >
+ default_dram_attrs.read_bandwidth) {
+ pr_info("hmat: hmem attrs for DRAM nodes mismatch.\n");
+ pr_info(" node %d:", nid_dram);
+ dump_hmem_attrs(&default_dram_attrs);
+ pr_info(" node %d:", nid);
+ dump_hmem_attrs(attrs);
+ pr_info(" disable hmat based abstract distance algorithm.\n");
+ disable_hmat_adistance_algorithm();
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static int hmat_calculate_adistance(struct notifier_block *self,
+ unsigned long nid, void *data)
+{
+ static DECLARE_BITMAP(p_nodes, MAX_NUMNODES);
+ struct memory_target *target;
+ struct node_hmem_attrs *attrs;
+ int *adist = data;
+ int pxm;
+
+ if (hmat_adistance_disabled)
+ return NOTIFY_OK;
+
+ pxm = node_to_pxm(nid);
+ target = find_mem_target(pxm);
+ if (!target)
+ return NOTIFY_OK;
+
+ if (hmat_init_default_dram_attrs())
+ return NOTIFY_OK;
+
+ mutex_lock(&target_lock);
+ hmat_update_target_attrs(target, p_nodes, 1);
+ mutex_unlock(&target_lock);
+
+ attrs = &target->hmem_attrs[1];
+
+ if (attrs->read_latency + attrs->write_latency == 0 ||
+ attrs->read_bandwidth + attrs->write_bandwidth == 0)
+ return NOTIFY_OK;
+
+ /*
+ * The abstract distance of a memory node is in direct
+ * proportion to its memory latency (read + write) and
+ * inversely proportional to its memory bandwidth (read +
+ * write). The abstract distance, memory latency, and memory
+ * bandwidth of the default DRAM nodes are used as the base.
+ */
+ *adist = MEMTIER_ADISTANCE_DRAM *
+ (attrs->read_latency + attrs->write_latency) /
+ (default_dram_attrs.read_latency +
+ default_dram_attrs.write_latency) *
+ (default_dram_attrs.read_bandwidth +
+ default_dram_attrs.write_bandwidth) /
+ (attrs->read_bandwidth + attrs->write_bandwidth);
+
+ return NOTIFY_STOP;
+}
+
+static __meminitdata struct notifier_block hmat_adist_nb =
+{
+ .notifier_call = hmat_calculate_adistance,
+ .priority = 100,
+};
+
static __init void hmat_free_structures(void)
{
struct memory_target *target, *tnext;
@@ -801,6 +933,7 @@ static __init int hmat_init(void)
struct acpi_table_header *tbl;
enum acpi_hmat_type i;
acpi_status status;
+ int usage;
if (srat_disabled() || hmat_disable)
return 0;
@@ -841,8 +974,11 @@ static __init int hmat_init(void)
hmat_register_targets();
/* Keep the table and structures if the notifier may use them */
- if (!hotplug_memory_notifier(hmat_callback, HMAT_CALLBACK_PRI))
+ usage = !hotplug_memory_notifier(hmat_callback, HMAT_CALLBACK_PRI);
+ usage += !register_mt_adistance_algorithm(&hmat_adist_nb);
+ if (usage)
return 0;
+
out_put:
hmat_free_structures();
acpi_put_table(tbl);
diff --git a/include/linux/memory-tiers.h b/include/linux/memory-tiers.h
index c6429e624244..9377239c8d34 100644
--- a/include/linux/memory-tiers.h
+++ b/include/linux/memory-tiers.h
@@ -33,6 +33,7 @@ struct memory_dev_type {
#ifdef CONFIG_NUMA
extern bool numa_demotion_enabled;
+extern struct memory_dev_type *default_dram_type;
struct memory_dev_type *alloc_memory_type(int adistance);
void destroy_memory_type(struct memory_dev_type *memtype);
void init_node_memory_type(int node, struct memory_dev_type *default_type);
@@ -64,6 +65,7 @@ static inline bool node_is_toptier(int node)
#else
#define numa_demotion_enabled false
+#define default_dram_type NULL
/*
* CONFIG_NUMA implementation returns non NULL error.
*/
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 1e55fbe2ad51..9a734ef2edfb 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -37,7 +37,7 @@ struct node_memory_type_map {
static DEFINE_MUTEX(memory_tier_lock);
static LIST_HEAD(memory_tiers);
static struct node_memory_type_map node_memory_types[MAX_NUMNODES];
-static struct memory_dev_type *default_dram_type;
+struct memory_dev_type *default_dram_type;
static struct bus_type memory_tier_subsys = {
.name = "memory_tiering",
--
2.39.2
Huang Ying <[email protected]> writes:
> A memory tiering abstract distance calculation algorithm based on ACPI
> HMAT is implemented. The basic idea is as follows.
>
> The performance attributes of system default DRAM nodes are recorded
> as the base line. Whose abstract distance is MEMTIER_ADISTANCE_DRAM.
> Then, the ratio of the abstract distance of a memory node (target) to
> MEMTIER_ADISTANCE_DRAM is scaled based on the ratio of the performance
> attributes of the node to that of the default DRAM nodes.
The problem I encountered here with the calculations is that HBM memory
ended up in a lower-tiered node which isn't what I wanted (at least when
that HBM is attached to a GPU say).
I suspect this is because the calculations are based on the CPU
point-of-view (access1) which still sees lower bandwidth to remote HBM
than local DRAM, even though the remote GPU has higher bandwidth access
to that memory. Perhaps we need to be considering access0 as well?
Ie. HBM directly attached to a generic initiator should be in a higher
tier regardless of CPU access characteristics?
That said I'm not entirely convinced the HMAT tables I'm testing against
are accurate/complete.
> Signed-off-by: "Huang, Ying" <[email protected]>
> Cc: Aneesh Kumar K.V <[email protected]>
> Cc: Wei Xu <[email protected]>
> Cc: Alistair Popple <[email protected]>
> Cc: Dan Williams <[email protected]>
> Cc: Dave Hansen <[email protected]>
> Cc: Davidlohr Bueso <[email protected]>
> Cc: Johannes Weiner <[email protected]>
> Cc: Jonathan Cameron <[email protected]>
> Cc: Michal Hocko <[email protected]>
> Cc: Yang Shi <[email protected]>
> Cc: Rafael J Wysocki <[email protected]>
> ---
> drivers/acpi/numa/hmat.c | 138 ++++++++++++++++++++++++++++++++++-
> include/linux/memory-tiers.h | 2 +
> mm/memory-tiers.c | 2 +-
> 3 files changed, 140 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c
> index 2dee0098f1a9..306a912090f0 100644
> --- a/drivers/acpi/numa/hmat.c
> +++ b/drivers/acpi/numa/hmat.c
> @@ -24,6 +24,7 @@
> #include <linux/node.h>
> #include <linux/sysfs.h>
> #include <linux/dax.h>
> +#include <linux/memory-tiers.h>
>
> static u8 hmat_revision;
> static int hmat_disable __initdata;
> @@ -759,6 +760,137 @@ static int hmat_callback(struct notifier_block *self,
> return NOTIFY_OK;
> }
>
> +static int hmat_adistance_disabled;
> +static struct node_hmem_attrs default_dram_attrs;
> +
> +static void dump_hmem_attrs(struct node_hmem_attrs *attrs)
> +{
> + pr_cont("read_latency: %u, write_latency: %u, read_bandwidth: %u, write_bandwidth: %u\n",
> + attrs->read_latency, attrs->write_latency,
> + attrs->read_bandwidth, attrs->write_bandwidth);
> +}
> +
> +static void disable_hmat_adistance_algorithm(void)
> +{
> + hmat_adistance_disabled = true;
> +}
> +
> +static int hmat_init_default_dram_attrs(void)
> +{
> + struct memory_target *target;
> + struct node_hmem_attrs *attrs;
> + int nid, pxm;
> + int nid_dram = NUMA_NO_NODE;
> +
> + if (default_dram_attrs.read_latency +
> + default_dram_attrs.write_latency != 0)
> + return 0;
> +
> + if (!default_dram_type)
> + return -EIO;
> +
> + for_each_node_mask(nid, default_dram_type->nodes) {
> + pxm = node_to_pxm(nid);
> + target = find_mem_target(pxm);
> + if (!target)
> + continue;
> + attrs = &target->hmem_attrs[1];
> + if (nid_dram == NUMA_NO_NODE) {
> + if (attrs->read_latency + attrs->write_latency == 0 ||
> + attrs->read_bandwidth + attrs->write_bandwidth == 0) {
> + pr_info("hmat: invalid hmem attrs for default DRAM node: %d,\n",
> + nid);
> + pr_info(" ");
> + dump_hmem_attrs(attrs);
> + pr_info(" disable hmat based abstract distance algorithm.\n");
> + disable_hmat_adistance_algorithm();
> + return -EIO;
> + }
> + nid_dram = nid;
> + default_dram_attrs = *attrs;
> + continue;
> + }
> +
> + /*
> + * The performance of all default DRAM nodes is expected
> + * to be same (that is, the variation is less than 10%).
> + * And it will be used as base to calculate the abstract
> + * distance of other memory nodes.
> + */
> + if (abs(attrs->read_latency - default_dram_attrs.read_latency) * 10 >
> + default_dram_attrs.read_latency ||
> + abs(attrs->write_latency - default_dram_attrs.write_latency) * 10 >
> + default_dram_attrs.write_latency ||
> + abs(attrs->read_bandwidth - default_dram_attrs.read_bandwidth) * 10 >
> + default_dram_attrs.read_bandwidth) {
> + pr_info("hmat: hmem attrs for DRAM nodes mismatch.\n");
> + pr_info(" node %d:", nid_dram);
> + dump_hmem_attrs(&default_dram_attrs);
> + pr_info(" node %d:", nid);
> + dump_hmem_attrs(attrs);
> + pr_info(" disable hmat based abstract distance algorithm.\n");
> + disable_hmat_adistance_algorithm();
> + return -EIO;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int hmat_calculate_adistance(struct notifier_block *self,
> + unsigned long nid, void *data)
> +{
> + static DECLARE_BITMAP(p_nodes, MAX_NUMNODES);
> + struct memory_target *target;
> + struct node_hmem_attrs *attrs;
> + int *adist = data;
> + int pxm;
> +
> + if (hmat_adistance_disabled)
> + return NOTIFY_OK;
> +
> + pxm = node_to_pxm(nid);
> + target = find_mem_target(pxm);
> + if (!target)
> + return NOTIFY_OK;
> +
> + if (hmat_init_default_dram_attrs())
> + return NOTIFY_OK;
> +
> + mutex_lock(&target_lock);
> + hmat_update_target_attrs(target, p_nodes, 1);
> + mutex_unlock(&target_lock);
> +
> + attrs = &target->hmem_attrs[1];
> +
> + if (attrs->read_latency + attrs->write_latency == 0 ||
> + attrs->read_bandwidth + attrs->write_bandwidth == 0)
> + return NOTIFY_OK;
> +
> + /*
> + * The abstract distance of a memory node is in direct
> + * proportion to its memory latency (read + write) and
> + * inversely proportional to its memory bandwidth (read +
> + * write). The abstract distance, memory latency, and memory
> + * bandwidth of the default DRAM nodes are used as the base.
> + */
> + *adist = MEMTIER_ADISTANCE_DRAM *
> + (attrs->read_latency + attrs->write_latency) /
> + (default_dram_attrs.read_latency +
> + default_dram_attrs.write_latency) *
> + (default_dram_attrs.read_bandwidth +
> + default_dram_attrs.write_bandwidth) /
> + (attrs->read_bandwidth + attrs->write_bandwidth);
> +
> + return NOTIFY_STOP;
> +}
> +
> +static __meminitdata struct notifier_block hmat_adist_nb =
> +{
> + .notifier_call = hmat_calculate_adistance,
> + .priority = 100,
> +};
> +
> static __init void hmat_free_structures(void)
> {
> struct memory_target *target, *tnext;
> @@ -801,6 +933,7 @@ static __init int hmat_init(void)
> struct acpi_table_header *tbl;
> enum acpi_hmat_type i;
> acpi_status status;
> + int usage;
>
> if (srat_disabled() || hmat_disable)
> return 0;
> @@ -841,8 +974,11 @@ static __init int hmat_init(void)
> hmat_register_targets();
>
> /* Keep the table and structures if the notifier may use them */
> - if (!hotplug_memory_notifier(hmat_callback, HMAT_CALLBACK_PRI))
> + usage = !hotplug_memory_notifier(hmat_callback, HMAT_CALLBACK_PRI);
> + usage += !register_mt_adistance_algorithm(&hmat_adist_nb);
> + if (usage)
> return 0;
> +
> out_put:
> hmat_free_structures();
> acpi_put_table(tbl);
> diff --git a/include/linux/memory-tiers.h b/include/linux/memory-tiers.h
> index c6429e624244..9377239c8d34 100644
> --- a/include/linux/memory-tiers.h
> +++ b/include/linux/memory-tiers.h
> @@ -33,6 +33,7 @@ struct memory_dev_type {
>
> #ifdef CONFIG_NUMA
> extern bool numa_demotion_enabled;
> +extern struct memory_dev_type *default_dram_type;
> struct memory_dev_type *alloc_memory_type(int adistance);
> void destroy_memory_type(struct memory_dev_type *memtype);
> void init_node_memory_type(int node, struct memory_dev_type *default_type);
> @@ -64,6 +65,7 @@ static inline bool node_is_toptier(int node)
> #else
>
> #define numa_demotion_enabled false
> +#define default_dram_type NULL
> /*
> * CONFIG_NUMA implementation returns non NULL error.
> */
> diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
> index 1e55fbe2ad51..9a734ef2edfb 100644
> --- a/mm/memory-tiers.c
> +++ b/mm/memory-tiers.c
> @@ -37,7 +37,7 @@ struct node_memory_type_map {
> static DEFINE_MUTEX(memory_tier_lock);
> static LIST_HEAD(memory_tiers);
> static struct node_memory_type_map node_memory_types[MAX_NUMNODES];
> -static struct memory_dev_type *default_dram_type;
> +struct memory_dev_type *default_dram_type;
>
> static struct bus_type memory_tier_subsys = {
> .name = "memory_tiering",
Alistair Popple <[email protected]> writes:
> Huang Ying <[email protected]> writes:
>
>> A memory tiering abstract distance calculation algorithm based on ACPI
>> HMAT is implemented. The basic idea is as follows.
>>
>> The performance attributes of system default DRAM nodes are recorded
>> as the base line. Whose abstract distance is MEMTIER_ADISTANCE_DRAM.
>> Then, the ratio of the abstract distance of a memory node (target) to
>> MEMTIER_ADISTANCE_DRAM is scaled based on the ratio of the performance
>> attributes of the node to that of the default DRAM nodes.
>
> The problem I encountered here with the calculations is that HBM memory
> ended up in a lower-tiered node which isn't what I wanted (at least when
> that HBM is attached to a GPU say).
I have tested the series on a server machine with HBM (pure HBM, not
attached to a GPU). Where, HBM is placed in a higher tier than DRAM.
> I suspect this is because the calculations are based on the CPU
> point-of-view (access1) which still sees lower bandwidth to remote HBM
> than local DRAM, even though the remote GPU has higher bandwidth access
> to that memory. Perhaps we need to be considering access0 as well?
> Ie. HBM directly attached to a generic initiator should be in a higher
> tier regardless of CPU access characteristics?
What's your requirements for memory tiers on the machine? I guess you
want to put GPU attache HBM in a higher tier and put DRAM in a lower
tier. So, cold HBM pages can be demoted to DRAM when there are memory
pressure on HBM? This sounds reasonable from GPU point of view.
The above requirements may be satisfied via calculating abstract
distance based on access0 (or combined with access1). But I suspect
this will be a general solution. I guess that any memory devices that
are used mainly by the memory initiators other than CPUs want to put
themselves in a higher memory tier than DRAM, regardless of its
access0.
One solution is to put GPU HBM in the highest memory tier (with smallest
abstract distance) always in GPU device driver regardless its HMAT
performance attributes. Is it possible?
> That said I'm not entirely convinced the HMAT tables I'm testing against
> are accurate/complete.
--
Best Regards,
Huang, Ying