mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault
@ 2026-11-02  6:01 Qiliang Yuan
  2026-11-02  6:01 ` [PATCH v2 1/2] mm/compaction: keep compaction deferral state per migration mode Qiliang Yuan
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Qiliang Yuan @ 2026-11-02  6:01 UTC (permalink / raw)
  To: Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang,
	David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Brendan Jackman, Johannes Weiner, Zi Yan
  Cc: linux-mm, linux-kernel, linux-trace-kernel, Qiliang Yuan

Mooncake, the KV cache store used with vLLM and SGLang, backs each
rank's host segment with MADV_HUGEPAGE memory and registers it with
the EFA NIC, which long-term pins it. Its commit c3fa13ecaf10 [1]
reports:

  "under the default local NUMA policy every THP fault on a full node
   runs direct compaction before falling back. With 8 ranks x 190GB
   this made registration take ~38 minutes."

Mooncake now binds the segment MPOL_INTERLEAVE across all allowed
nodes, so that no node fills before the others. That gives up
node-local placement, and a task that sets its own memory policy
still gets the stall.

A THP fault on a MADV_HUGEPAGE VMA tries the local node first, with a
single round of async direct compaction, before falling back to other
nodes. A failed async run is never deferred, so when the free memory
of the local node is scattered between pages that can't be migrated,
such as long-term pins, every 2M fault scans the zone again and fails.
A node that is simply full doesn't pay this: compaction_suitable()
turns compaction away before it scans anything.

Patch 1 splits the deferral state by migration mode. Patch 2 defers
async compaction on its own state after it fails, leaving sync
compaction as it is.

Populating a 4 GiB MADV_HUGEPAGE buffer on a node fragmented by pinned
memory, direct compactions drop from 649-1473 to 9-41 and the time
spent in compaction from 118.6-264.2 ms to 2.6-5.1 ms, with no fewer
THPs on the local node. Details are in patch 2.

[1] https://github.com/kvcache-ai/Mooncake/commit/c3fa13ecaf1038df933dcb784ff8ffe026381100

Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
V1 -> V2:
- Describe the Mooncake report in the cover letter
- Emit mm_compaction_defer_reset for both states (Sashiko)
- Reset the pageblock skip hints only from sync compaction (Sashiko)
- Rebase on v7.4-rc1 and rerun the tests

v1: https://lore.kernel.org/r/20261001-bug-mm-thp-async-compact-defer-v1-0-0174c7923430@gmail.com

---
Qiliang Yuan (2):
      mm/compaction: keep compaction deferral state per migration mode
      mm/compaction: defer failed async direct compaction

 include/linux/mmzone.h            |  7 ++--
 include/trace/events/compaction.h | 27 +++++++------
 mm/compaction.c                   | 82 ++++++++++++++++++++++-----------------
 3 files changed, 66 insertions(+), 50 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261001-bug-mm-thp-async-compact-defer-b83351bfb25d

Best regards,
-- 
Qiliang Yuan <odys.yuan@gmail.com>


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/2] mm/compaction: keep compaction deferral state per migration mode
  2026-11-02  6:01 [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Qiliang Yuan
@ 2026-11-02  6:01 ` Qiliang Yuan
  2026-11-02  6:01 ` [PATCH v2 2/2] mm/compaction: defer failed async direct compaction Qiliang Yuan
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ messages in thread
From: Qiliang Yuan @ 2026-11-02  6:01 UTC (permalink / raw)
  To: Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang,
	David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Brendan Jackman, Johannes Weiner, Zi Yan
  Cc: linux-mm, linux-kernel, linux-trace-kernel, Qiliang Yuan

A zone keeps one compaction deferral state, compact_considered,
compact_defer_shift and compact_order_failed, for all migration modes.
Only sync compaction failures defer a zone, so a failed async direct
compaction is never deferred.

Deferring async compaction on that shared state would defer sync
compaction along with it, though sync compaction can still succeed on
the pageblocks async compaction skips.

Turn the three fields into arrays indexed by sync, like
compact_cached_migrate_pfn, and pass the mode to the deferral helpers.
All callers use the sync state for now, and a reset clears both. Add a
sync field to the deferral tracepoints, and emit
mm_compaction_defer_reset once for each state a reset clears.

Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
 include/linux/mmzone.h            |  7 +++--
 include/trace/events/compaction.h | 27 +++++++++--------
 mm/compaction.c                   | 62 +++++++++++++++++++++------------------
 3 files changed, 52 insertions(+), 44 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ecd1ebd49756c..73da870fff975 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1150,10 +1150,11 @@ struct zone {
 	 * are skipped before trying again. The number attempted since
 	 * last failure is tracked with compact_considered.
 	 * compact_order_failed is the minimum compaction failed order.
+	 * Indexed by sync, like compact_cached_migrate_pfn.
 	 */
-	unsigned int		compact_considered;
-	unsigned int		compact_defer_shift;
-	int			compact_order_failed;
+	unsigned int		compact_considered[ASYNC_AND_SYNC];
+	unsigned int		compact_defer_shift[ASYNC_AND_SYNC];
+	int			compact_order_failed[ASYNC_AND_SYNC];
 #endif
 
 #if defined CONFIG_COMPACTION || defined CONFIG_CMA
diff --git a/include/trace/events/compaction.h b/include/trace/events/compaction.h
index d05759d185389..23ac6e1750623 100644
--- a/include/trace/events/compaction.h
+++ b/include/trace/events/compaction.h
@@ -238,14 +238,15 @@ DEFINE_EVENT(mm_compaction_suitable_template, mm_compaction_suitable,
 
 DECLARE_EVENT_CLASS(mm_compaction_defer_template,
 
-	TP_PROTO(struct zone *zone, int order),
+	TP_PROTO(struct zone *zone, int order, bool sync),
 
-	TP_ARGS(zone, order),
+	TP_ARGS(zone, order, sync),
 
 	TP_STRUCT__entry(
 		__field(int, nid)
 		__field(enum zone_type, idx)
 		__field(int, order)
+		__field(bool, sync)
 		__field(unsigned int, considered)
 		__field(unsigned int, defer_shift)
 		__field(int, order_failed)
@@ -255,15 +256,17 @@ DECLARE_EVENT_CLASS(mm_compaction_defer_template,
 		__entry->nid = zone_to_nid(zone);
 		__entry->idx = zone_idx(zone);
 		__entry->order = order;
-		__entry->considered = zone->compact_considered;
-		__entry->defer_shift = zone->compact_defer_shift;
-		__entry->order_failed = zone->compact_order_failed;
+		__entry->sync = sync;
+		__entry->considered = zone->compact_considered[sync];
+		__entry->defer_shift = zone->compact_defer_shift[sync];
+		__entry->order_failed = zone->compact_order_failed[sync];
 	),
 
-	TP_printk("node=%d zone=%-8s order=%d order_failed=%d consider=%u limit=%lu",
+	TP_printk("node=%d zone=%-8s order=%d sync=%d order_failed=%d consider=%u limit=%lu",
 		__entry->nid,
 		__print_symbolic(__entry->idx, ZONE_TYPE),
 		__entry->order,
+		__entry->sync,
 		__entry->order_failed,
 		__entry->considered,
 		1UL << __entry->defer_shift)
@@ -271,23 +274,23 @@ DECLARE_EVENT_CLASS(mm_compaction_defer_template,
 
 DEFINE_EVENT(mm_compaction_defer_template, mm_compaction_deferred,
 
-	TP_PROTO(struct zone *zone, int order),
+	TP_PROTO(struct zone *zone, int order, bool sync),
 
-	TP_ARGS(zone, order)
+	TP_ARGS(zone, order, sync)
 );
 
 DEFINE_EVENT(mm_compaction_defer_template, mm_compaction_defer_compaction,
 
-	TP_PROTO(struct zone *zone, int order),
+	TP_PROTO(struct zone *zone, int order, bool sync),
 
-	TP_ARGS(zone, order)
+	TP_ARGS(zone, order, sync)
 );
 
 DEFINE_EVENT(mm_compaction_defer_template, mm_compaction_defer_reset,
 
-	TP_PROTO(struct zone *zone, int order),
+	TP_PROTO(struct zone *zone, int order, bool sync),
 
-	TP_ARGS(zone, order)
+	TP_ARGS(zone, order, sync)
 );
 
 TRACE_EVENT(mm_compaction_kcompactd_sleep,
diff --git a/mm/compaction.c b/mm/compaction.c
index f1b2060eb2016..e7a450b2be757 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -124,35 +124,35 @@ static unsigned long release_free_list(struct list_head *freepages)
  * allocation success. 1 << compact_defer_shift, compactions are skipped up
  * to a limit of 1 << COMPACT_MAX_DEFER_SHIFT
  */
-static void defer_compaction(struct zone *zone, int order)
+static void defer_compaction(struct zone *zone, int order, bool sync)
 {
-	zone->compact_considered = 0;
-	zone->compact_defer_shift++;
+	zone->compact_considered[sync] = 0;
+	zone->compact_defer_shift[sync]++;
 
-	if (order < zone->compact_order_failed)
-		zone->compact_order_failed = order;
+	if (order < zone->compact_order_failed[sync])
+		zone->compact_order_failed[sync] = order;
 
-	if (zone->compact_defer_shift > COMPACT_MAX_DEFER_SHIFT)
-		zone->compact_defer_shift = COMPACT_MAX_DEFER_SHIFT;
+	if (zone->compact_defer_shift[sync] > COMPACT_MAX_DEFER_SHIFT)
+		zone->compact_defer_shift[sync] = COMPACT_MAX_DEFER_SHIFT;
 
-	trace_mm_compaction_defer_compaction(zone, order);
+	trace_mm_compaction_defer_compaction(zone, order, sync);
 }
 
 /* Returns true if compaction should be skipped this time */
-static bool compaction_deferred(struct zone *zone, int order)
+static bool compaction_deferred(struct zone *zone, int order, bool sync)
 {
-	unsigned long defer_limit = 1UL << zone->compact_defer_shift;
+	unsigned long defer_limit = 1UL << zone->compact_defer_shift[sync];
 
-	if (order < zone->compact_order_failed)
+	if (order < zone->compact_order_failed[sync])
 		return false;
 
 	/* Avoid possible overflow */
-	if (++zone->compact_considered >= defer_limit) {
-		zone->compact_considered = defer_limit;
+	if (++zone->compact_considered[sync] >= defer_limit) {
+		zone->compact_considered[sync] = defer_limit;
 		return false;
 	}
 
-	trace_mm_compaction_deferred(zone, order);
+	trace_mm_compaction_deferred(zone, order, sync);
 
 	return true;
 }
@@ -165,24 +165,28 @@ static bool compaction_deferred(struct zone *zone, int order)
 void compaction_defer_reset(struct zone *zone, int order,
 		bool alloc_success)
 {
-	if (alloc_success) {
-		zone->compact_considered = 0;
-		zone->compact_defer_shift = 0;
-	}
-	if (order >= zone->compact_order_failed)
-		zone->compact_order_failed = order + 1;
+	int sync;
+
+	for (sync = 0; sync < ASYNC_AND_SYNC; sync++) {
+		if (alloc_success) {
+			zone->compact_considered[sync] = 0;
+			zone->compact_defer_shift[sync] = 0;
+		}
+		if (order >= zone->compact_order_failed[sync])
+			zone->compact_order_failed[sync] = order + 1;
 
-	trace_mm_compaction_defer_reset(zone, order);
+		trace_mm_compaction_defer_reset(zone, order, sync);
+	}
 }
 
-/* Returns true if restarting compaction after many failures */
+/* Return true if restarting sync compaction after many failures */
 static bool compaction_restarting(struct zone *zone, int order)
 {
-	if (order < zone->compact_order_failed)
+	if (order < zone->compact_order_failed[true])
 		return false;
 
-	return zone->compact_defer_shift == COMPACT_MAX_DEFER_SHIFT &&
-		zone->compact_considered >= 1UL << zone->compact_defer_shift;
+	return zone->compact_defer_shift[true] == COMPACT_MAX_DEFER_SHIFT &&
+		zone->compact_considered[true] >= 1UL << zone->compact_defer_shift[true];
 }
 
 /* Returns true if the pageblock should be scanned for pages to isolate. */
@@ -2855,7 +2859,7 @@ enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order,
 				continue;
 
 		if (prio > MIN_COMPACT_PRIORITY
-					&& compaction_deferred(zone, order)) {
+					&& compaction_deferred(zone, order, true)) {
 			rc = max_t(enum compact_result, COMPACT_DEFERRED, rc);
 			continue;
 		}
@@ -2893,7 +2897,7 @@ enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order,
 			 * so we defer compaction there. If it ends up
 			 * succeeding after all, it will be reset.
 			 */
-			defer_compaction(zone, order);
+			defer_compaction(zone, order, true);
 
 		/*
 		 * We might have stopped compacting due to need_resched() in
@@ -3111,7 +3115,7 @@ static void kcompactd_do_work(pg_data_t *pgdat)
 		if (!populated_zone(zone))
 			continue;
 
-		if (compaction_deferred(zone, cc.order))
+		if (compaction_deferred(zone, cc.order, true))
 			continue;
 
 		ret = compaction_suit_allocation_order(zone,
@@ -3141,7 +3145,7 @@ static void kcompactd_do_work(pg_data_t *pgdat)
 			 * We use sync migration mode here, so we defer like
 			 * sync direct compaction does.
 			 */
-			defer_compaction(zone, cc.order);
+			defer_compaction(zone, cc.order, true);
 		}
 
 		count_compact_events(KCOMPACTD_MIGRATE_SCANNED,

-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 2/2] mm/compaction: defer failed async direct compaction
  2026-11-02  6:01 [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Qiliang Yuan
  2026-11-02  6:01 ` [PATCH v2 1/2] mm/compaction: keep compaction deferral state per migration mode Qiliang Yuan
@ 2026-11-02  6:01 ` Qiliang Yuan
  2026-11-10 14:42   ` Qiliang Yuan
  2026-11-02  9:23 ` [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Lorenzo Stoakes (ARM)
  2026-11-10 13:58 ` Qiliang Yuan
  3 siblings, 1 reply; 8+ messages in thread
From: Qiliang Yuan @ 2026-11-02  6:01 UTC (permalink / raw)
  To: Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang,
	David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Brendan Jackman, Johannes Weiner, Zi Yan
  Cc: linux-mm, linux-kernel, linux-trace-kernel, Qiliang Yuan

A THP fault on a MADV_HUGEPAGE VMA first tries the local node only,
with __GFP_THISNODE | __GFP_NORETRY, and runs a single round of async
direct compaction there before falling back to other nodes. A failed
async run is never deferred.

When the local node has plenty of free memory but no free pageblock,
and what sits between the free pages can't be migrated, such as memory
long-term pinned for RDMA, each of these faults scans the zone again
and fails before falling back.

Defer the async state when an async run fails, and check it before
further async runs. Sync compaction keeps deferring on its own state,
and a successful compaction or allocation resets both. Reset the
pageblock skip hints only from sync compaction, as async relies on
them.

Populating a 4 GiB MADV_HUGEPAGE buffer from node 0 of a two-node VM,
with node 0 (24 GiB) fragmented by long-term pinning every other page,
median of 3 runs on each of two boots per kernel:

                          before              after
  direct compactions      649 / 1473          9 / 41
  time in compaction      118.6 / 264.2 ms    2.6 / 5.1 ms
  population time         531 / 504 ms        390 / 261 ms
  THPs on node 0          1399 / 575          1478 / 727

With movable memory in place of the pins, compaction still succeeds
and both kernels get all 2048 THPs on node 0.

Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
 mm/compaction.c | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/mm/compaction.c b/mm/compaction.c
index e7a450b2be757..390171370b046 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -2600,9 +2600,12 @@ compact_zone(struct compact_control *cc, struct capture_control *capc)
 
 	/*
 	 * Clear pageblock skip if there were failures recently and compaction
-	 * is about to be retried after being deferred.
+	 * is about to be retried after being deferred. Leave this to sync
+	 * compaction: async compaction relies on the skip hints, and clearing
+	 * them on its retries would rescan the whole zone each time.
 	 */
-	if (compaction_restarting(cc->zone, cc->order))
+	if (cc->mode != MIGRATE_ASYNC &&
+	    compaction_restarting(cc->zone, cc->order))
 		__reset_isolation_suitable(cc->zone);
 
 	/*
@@ -2858,8 +2861,10 @@ enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order,
 			!__cpuset_zone_allowed(zone, gfp_mask))
 				continue;
 
-		if (prio > MIN_COMPACT_PRIORITY
-					&& compaction_deferred(zone, order, true)) {
+		if (prio > MIN_COMPACT_PRIORITY &&
+		    (compaction_deferred(zone, order, true) ||
+		     (prio == COMPACT_PRIO_ASYNC &&
+		      compaction_deferred(zone, order, false)))) {
 			rc = max_t(enum compact_result, COMPACT_DEFERRED, rc);
 			continue;
 		}
@@ -2890,14 +2895,17 @@ enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order,
 			break;
 		}
 
-		if (prio != COMPACT_PRIO_ASYNC && (status == COMPACT_COMPLETE ||
-					status == COMPACT_PARTIAL_SKIPPED))
+		if (status == COMPACT_COMPLETE ||
+		    status == COMPACT_PARTIAL_SKIPPED)
 			/*
 			 * We think that allocation won't succeed in this zone
 			 * so we defer compaction there. If it ends up
-			 * succeeding after all, it will be reset.
+			 * succeeding after all, it will be reset. Let a failed
+			 * async run defer only further async runs, as sync
+			 * compaction may succeed on pageblocks it skipped.
 			 */
-			defer_compaction(zone, order, true);
+			defer_compaction(zone, order,
+					 prio != COMPACT_PRIO_ASYNC);
 
 		/*
 		 * We might have stopped compacting due to need_resched() in

-- 
2.43.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault
  2026-11-02  6:01 [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Qiliang Yuan
  2026-11-02  6:01 ` [PATCH v2 1/2] mm/compaction: keep compaction deferral state per migration mode Qiliang Yuan
  2026-11-02  6:01 ` [PATCH v2 2/2] mm/compaction: defer failed async direct compaction Qiliang Yuan
@ 2026-11-02  9:23 ` Lorenzo Stoakes (ARM)
  2026-11-10 13:58 ` Qiliang Yuan
  3 siblings, 0 replies; 8+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-02  9:23 UTC (permalink / raw)
  To: Qiliang Yuan
  Cc: Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang,
	David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman,
	Johannes Weiner, Zi Yan, linux-mm, linux-kernel,
	linux-trace-kernel

You had 2 maintainers NAK your v1, why on earth are you sending a v2?

For avoidance of doubt:

Nacked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

For this and any future revisions. Stop sending it.

I have no confidence you understand any of this.

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault
  2026-11-02  6:01 [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Qiliang Yuan
                   ` (2 preceding siblings ...)
  2026-11-02  9:23 ` [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Lorenzo Stoakes (ARM)
@ 2026-11-10 13:58 ` Qiliang Yuan
  2026-11-10 14:13   ` Vlastimil Babka (SUSE)
  3 siblings, 1 reply; 8+ messages in thread
From: Qiliang Yuan @ 2026-11-10 13:58 UTC (permalink / raw)
  To: Vlastimil Babka, Johannes Weiner
  Cc: Qiliang Yuan, Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt,
	Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He,
	Baolin Wang, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman, Zi Yan,
	linux-mm, linux-kernel, linux-trace-kernel

Hi Vlastimil, Johannes,

Could you share your view on this series when you have time?

One known issue in v2: the cc->mode != MIGRATE_ASYNC check added to
2/2 blocks clearing stale skip hints once sync compaction is fully
deferred, so local THP may not recover after the pins are released.
I'd drop it in v3, but won't send one before hearing from you.

Thanks,
Qiliang

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault
  2026-11-10 13:58 ` Qiliang Yuan
@ 2026-11-10 14:13   ` Vlastimil Babka (SUSE)
  2026-11-10 15:39     ` Qiliang Yuan
  0 siblings, 1 reply; 8+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-11-10 14:13 UTC (permalink / raw)
  To: Qiliang Yuan, Johannes Weiner
  Cc: Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song,
	Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang,
	David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman, Zi Yan,
	linux-mm, linux-kernel, linux-trace-kernel

On 11/10/26 14:58, Qiliang Yuan wrote:
> Hi Vlastimil, Johannes,
> 
> Could you share your view on this series when you have time?

The series was nacked twice by senior maintainers. Due to dealing with
urgent issues [1] we might perhaps find time later to see if it reports a
real issue, and in that case fix it ourselves. We have currently no time to
help those who need AI assistance to contribute instead of having own knowledge.

[1]
https://lore.kernel.org/linux-mm/a2f584f6-e382-4876-aa6d-d5a34de6ee59@kernel.org/

> One known issue in v2: the cc->mode != MIGRATE_ASYNC check added to
> 2/2 blocks clearing stale skip hints once sync compaction is fully
> deferred, so local THP may not recover after the pins are released.
> I'd drop it in v3, but won't send one before hearing from you.
> 
> Thanks,
> Qiliang


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 2/2] mm/compaction: defer failed async direct compaction
  2026-11-02  6:01 ` [PATCH v2 2/2] mm/compaction: defer failed async direct compaction Qiliang Yuan
@ 2026-11-10 14:42   ` Qiliang Yuan
  0 siblings, 0 replies; 8+ messages in thread
From: Qiliang Yuan @ 2026-11-10 14:42 UTC (permalink / raw)
  To: Vlastimil Babka
  Cc: Qiliang Yuan, Andrew Morton, Kairui Song, Qi Zheng, Shakeel Butt,
	Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Baoquan He,
	Baolin Wang, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman,
	Johannes Weiner, Zi Yan, linux-mm, linux-kernel,
	linux-trace-kernel

On Mon, 02 Nov 2026 01:01:08 -0500, Qiliang Yuan wrote:
> @@ -2600,9 +2600,12 @@ compact_zone(struct compact_control *cc, struct capture_control *capc)
>  
>  	/*
>  	 * Clear pageblock skip if there were failures recently and compaction
> -	 * is about to be retried after being deferred.
> +	 * is about to be retried after being deferred. Leave this to sync
> +	 * compaction: async compaction relies on the skip hints, and clearing
> +	 * them on its retries would rescan the whole zone each time.
>  	 */
> -	if (compaction_restarting(cc->zone, cc->order))
> +	if (cc->mode != MIGRATE_ASYNC &&
> +	    compaction_restarting(cc->zone, cc->order))
>  		__reset_isolation_suitable(cc->zone);

As was pointed out to me off-list, this test removes a recovery path.
I'm not asking to reconsider the series, only leaving the analysis and
a reproducer here in case they help when you look at the issue.

The async run a THP fault does on the local node is never deferred:
try_to_compact_pages() calls defer_compaction() only for sync
priorities, because the deferral state is shared and deferring it
after an async failure would defer sync compaction too. The async path
checks that same sync state in compaction_deferred(), so as long as
sync isn't deferred, every fault scans the zone again. Patch 1 splits
the state so that async can be deferred on its own.

Skip hints are cleared in two places: in compact_zone(), when
compaction_restarting() sees the sync deferral at its maximum, and by
kswapd before it sleeps. Madvised THP faults don't wake kswapd, and
with proactive compaction off nothing else resets the hints, so once
the pinned pages become movable again, the reset in compact_zone() is
what lets local THP attempts use those pageblocks. After sync reaches
the maximum deferral, that reset comes from an async retry, which is
what the test in this patch blocks.

I reproduced it on the same QEMU guest as the series: 32 vCPUs,
node 0 with 16 CPUs and 24 GiB, node 1 with 16 CPUs and 8 GiB,
compaction_proactiveness set to 0 and defrag left at madvise:

  1. Fill node 0 with anonymous memory, free every other 4K page and
     long-term pin the rest through io_uring fixed buffers.
  2. With defrag set to always, fault in a 1 GiB MADV_HUGEPAGE buffer
     bound to node 0, so THP faults there run sync direct compaction
     and fail until sync compaction reaches the maximum deferral.
  3. Close the io_uring rings. The pins are gone, the memory stays in
     use and is movable again.
  4. Populate a 4 GiB MADV_HUGEPAGE buffer from a CPU of node 0.

Compactions that succeeded in step 4, one boot per number:

  mainline              1184, 963
  v2                    8, 0, 516, 400, 534, 521, 495
  v2 without the test   1025, 994, 906, 957

v2 recovered in five of seven boots without resetting any skip hint
(no call to __reset_isolation_suitable() in the two boots I traced):
with async compaction deferred, it scans few pageblocks while they are
pinned and leaves most of them unmarked. In the other two boots the
marked pageblocks kept it from recovering at all. Without the test,
the reset after the maximum sync deferral recovers every boot, as on
mainline, and the steady pinned case stays as cheap as with v2 (7.8
to 20 ms in compaction against 310 to 332 ms on mainline).

The reproducer is below, run as CPUS=0-15 sh thp-recover.sh.

Thanks,
Qiliang

---8<--- thp-recover.sh
#!/bin/sh
# Run as root on a two-node machine; CPUS lists the CPUs of node 0.
CPUS=${CPUS:-0}
D=/sys/kernel/mm/transparent_hugepage/defrag
echo 0 > /proc/sys/vm/compaction_proactiveness
echo madvise > $D
echo 1 > /proc/sys/vm/drop_caches

M=/sys/devices/system/node/node0/meminfo
free=$(awk '/MemFree/ { print int($4 / 1024) }' $M)
./thp-recover hog $((free - 300)) > hog.log &
hog=$!
until grep -q ready hog.log; do sleep 1; done

echo "steady, pinned:"
for i in 1 2 3; do taskset -c $CPUS ./thp-recover fault 4096; done

# Fail sync compaction on node 0 until it reaches the maximum deferral
echo always > $D
echo "bound to node 0:"
taskset -c $CPUS ./thp-recover bfault 1024
echo madvise > $D

kill -USR1 $hog
until grep -q unpinned hog.log; do sleep 1; done
echo "after dropping the pins:"
for i in 1 2 3 4 5; do taskset -c $CPUS ./thp-recover fault 4096; done
kill $hog

---8<--- thp-recover.c
// SPDX-License-Identifier: GPL-2.0
/*
 * thp-recover hog <mb>     fill node 0, free every other page and long-term
 *                          pin the rest; SIGUSR1 drops the pins and keeps
 *                          the memory in use
 * thp-recover fault <mb>   populate a MADV_HUGEPAGE buffer and print how
 *                          much of it got THPs and on which node
 * thp-recover bfault <mb>  the same, bound to node 0
 */
#define _GNU_SOURCE
#include <linux/io_uring.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <time.h>
#include <unistd.h>

#define MB		(1UL << 20)
#define MPOL_BIND	2
#define RING_BUFS	16384

static volatile sig_atomic_t unpin;

static void on_usr1(int sig)
{
	unpin = sig;
}

static void bind_node0(void)
{
	unsigned long mask = 1;

	if (syscall(__NR_set_mempolicy, MPOL_BIND, &mask, 64))
		perror("set_mempolicy"), exit(1);
}

static long vmstat(const char *key)
{
	char name[64];
	long v, ret = 0;
	FILE *f = fopen("/proc/vmstat", "r");

	while (f && fscanf(f, "%63s %ld", name, &v) == 2)
		if (!strcmp(name, key))
			ret = v;
	if (f)
		fclose(f);
	return ret;
}

static long thp_mb(void)
{
	const char *key = "AnonHugePages:";
	char line[256];
	long kb = 0;
	FILE *f = fopen("/proc/self/smaps_rollup", "r");

	while (f && fgets(line, sizeof(line), f))
		if (!strncmp(line, key, strlen(key)))
			kb = atol(line + strlen(key));
	if (f)
		fclose(f);
	return kb / 1024;
}

static int hog(size_t size)
{
	size_t npages = size / 8192, done = 0, i, off;
	struct iovec *iov = calloc(RING_BUFS, sizeof(*iov));
	int fds[4096], nr = 0, r;
	char *buf;

	bind_node0();
	buf = mmap(NULL, size, PROT_READ | PROT_WRITE,
		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	madvise(buf, size, MADV_NOHUGEPAGE);
	memset(buf, 1, size);
	/* Plenty of free memory, but no free 2M block */
	for (off = 4096; off < size; off += 8192)
		madvise(buf + off, 4096, MADV_DONTNEED);
	/* Long-term pin the pages left, as RDMA registration does */
	while (done < npages) {
		struct io_uring_params p = { 0 };
		size_t n = npages - done > RING_BUFS ? RING_BUFS : npages - done;
		int fd = syscall(__NR_io_uring_setup, 4, &p);

		for (i = 0; i < n; i++) {
			iov[i].iov_base = buf + (done + i) * 8192;
			iov[i].iov_len = 4096;
		}
		if (fd < 0 || syscall(__NR_io_uring_register, fd,
				      IORING_REGISTER_BUFFERS, iov, n))
			perror("pin"), exit(1);
		fds[nr++] = fd;
		done += n;
	}
	printf("ready\n");
	fflush(stdout);
	signal(SIGUSR1, on_usr1);
	while (!unpin)
		pause();
	for (r = 0; r < nr; r++)
		close(fds[r]);
	printf("unpinned\n");
	fflush(stdout);
	for (;;)
		pause();
}

static int fault(size_t size)
{
	size_t n = size / (2 * MB), i, node[2] = { 0 };
	long stall = vmstat("compact_stall"), ok = vmstat("compact_success");
	void **pages = malloc(n * sizeof(*pages));
	int *status = malloc(n * sizeof(*status));
	struct timespec t0, t1;
	double ms;
	char *buf;

	buf = mmap(NULL, size + 2 * MB, PROT_READ | PROT_WRITE,
		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	buf = (char *)(((unsigned long)buf + 2 * MB - 1) & ~(2 * MB - 1));
	madvise(buf, size, MADV_HUGEPAGE);
	clock_gettime(CLOCK_MONOTONIC, &t0);
	for (i = 0; i < size; i += 4096)
		buf[i] = 1;
	clock_gettime(CLOCK_MONOTONIC, &t1);
	ms = (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6;

	for (i = 0; i < n; i++)
		pages[i] = buf + i * 2 * MB;
	syscall(__NR_move_pages, 0, n, pages, NULL, status, 0);
	for (i = 0; i < n; i++)
		if (status[i] == 0 || status[i] == 1)
			node[status[i]]++;
	printf("%zu MB in %.0f ms, %ld MB in THPs, 2M ranges on node 0/1: %zu/%zu, ",
	       size / MB, ms, thp_mb(), node[0], node[1]);
	printf("compactions: %ld, succeeded: %ld\n",
	       vmstat("compact_stall") - stall, vmstat("compact_success") - ok);
	return 0;
}

int main(int argc, char **argv)
{
	size_t size;

	if (argc != 3) {
		fprintf(stderr, "usage: %s hog|fault|bfault <mb>\n", argv[0]);
		return 1;
	}
	size = strtoul(argv[2], NULL, 0) * MB;
	if (!strcmp(argv[1], "hog"))
		return hog(size);
	if (!strcmp(argv[1], "bfault"))
		bind_node0();
	return fault(size);
}

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault
  2026-11-10 14:13   ` Vlastimil Babka (SUSE)
@ 2026-11-10 15:39     ` Qiliang Yuan
  0 siblings, 0 replies; 8+ messages in thread
From: Qiliang Yuan @ 2026-11-10 15:39 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE)
  Cc: Qiliang Yuan, Johannes Weiner, Andrew Morton, Kairui Song,
	Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie,
	Wei Xu, Baoquan He, Baolin Wang, David Hildenbrand,
	Lorenzo Stoakes, Liam R. Howlett, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman, Zi Yan,
	linux-mm, linux-kernel, linux-trace-kernel

On 11/10/26 15:13, Vlastimil Babka wrote:
> The series was nacked twice by senior maintainers. Due to dealing with
> urgent issues [1] we might perhaps find time later to see if it reports a
> real issue, and in that case fix it ourselves. We have currently no time to
> help those who need AI assistance to contribute instead of having own knowledge.
>
> [1]
> https://lore.kernel.org/linux-mm/a2f584f6-e382-4876-aa6d-d5a34de6ee59@kernel.org/

I'm only spending time on this because it gets in the way of AI
training and inference, which long-term pin large buffers for RDMA.
Mooncake, the KV cache store used with vLLM and SGLang, hit it with
8 ranks x 190GB taking ~38 minutes to register. The stall and the
numbers are real, also measured on my own test setup.

Async failures have not been deferred since commit 66199712e9ee ("mm:
page allocator: do not call direct reclaim for THP allocations while
compaction is deferred"), which explained:

	/*
	 * As async compaction considers a subset of pageblocks, only
	 * defer if the failure was a sync compaction failure.
	 */

That still holds, so the series has no Fixes tag: it isn't a bug in
that commit. What the rule doesn't cover is long-term pinned memory,
much less common in 2012, where async fails the same way on every
fault and is never deferred. The series leaves sync deferral as it is
and gives async a state of its own. The analysis and a reproducer are
in [2].

If you look at it later, I'm happy to test whatever you come up with.

[2] https://lore.kernel.org/r/20261110144256.3841504-1-qiliang.yuan@linux.dev

Thanks,
Qiliang

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-11-10 15:39 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02  6:01 [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Qiliang Yuan
2026-11-02  6:01 ` [PATCH v2 1/2] mm/compaction: keep compaction deferral state per migration mode Qiliang Yuan
2026-11-02  6:01 ` [PATCH v2 2/2] mm/compaction: defer failed async direct compaction Qiliang Yuan
2026-11-10 14:42   ` Qiliang Yuan
2026-11-02  9:23 ` [PATCH v2 0/2] mm/compaction: stop repeating failed async compaction on every THP fault Lorenzo Stoakes (ARM)
2026-11-10 13:58 ` Qiliang Yuan
2026-11-10 14:13   ` Vlastimil Babka (SUSE)
2026-11-10 15:39     ` Qiliang Yuan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®