You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@geaflow.apache.org by "kaori-seasons (via GitHub)" <gi...@apache.org> on 2025/11/10 06:18:47 UTC

Re: [I] Support subgraph sampling for GNN [geaflow]

kaori-seasons commented on issue #502:
URL: https://github.com/apache/geaflow/issues/502#issuecomment-3509623766

   @Loognqiang Hello, I have designed a GNN sampling scheme. You can take a look.
   
   ### 1.1 Core Capabilities Already Available
   
   GeaFlow already provides solid infrastructure:
   
   **Edge Iterator Mechanism**: The system has implemented `EdgeScanIterator`, supporting iterative edge access and filtering [1](#1-0) .
   
   **Edge Limit Support**: The `IEdgeLimit` interface supports limiting the number of incoming and outgoing edges per vertex<cite/>.
   
   **Filter Pushdown**: `StatePushDown` supports pushing filter conditions and limits down to the storage layer<cite/>.
   
   **Vertex-Grouped Edge Iteration**: `EdgeListScanIterator` has implemented edge iteration grouped by source vertex ID [2](#1-1) .
   
   **Algorithm Runtime Context**: `AlgorithmRuntimeContext` provides iterator interfaces for loading edges<cite/>.
   
   **K-hop Algorithm Example**: The existing K-hop implementation demonstrates multi-layer neighbor traversal patterns<cite/>.
   
   ### 1.2 Capabilities Requiring Extension
   
   Current architecture **limitations**:
   1. `LimitFilter` only supports sequential limiting, not random sampling<cite/>
   2. No dedicated layered sampling API for GNN scenarios<cite/>
   3. Lack of flexible sampling strategy configuration (random sampling, importance sampling, etc.)<cite/>
   
   ## II. Core Design Plan
   
   ### 2.1 New GNN Sampling API Design
   
   #### 2.1.1 Sampling Configuration Interface
   
   ```java
   /**
    * GNN subgraph sampling configuration
    */
   public interface IGNNSamplingConfig extends Serializable {
       
       // Sample size configuration per layer
       List<Integer> getLayerSampleSizes();
       
       // Sampling depth (K)
       int getSamplingDepth();
       
       // Sampling strategy
       SamplingStrategy getStrategy();
       
       // Edge direction
       EdgeDirection getDirection();
       
       // Include vertex properties
       boolean includeVertexProperties();
       
       // Include edge properties
       boolean includeEdgeProperties();
   }
   ```
   
   #### 2.1.2 Sampling Strategy Enum
   
   ```java
   public enum SamplingStrategy {
       RANDOM,           // Random sampling
       TOP_K,            // Top K
       WEIGHTED_RANDOM,  // Weighted random sampling
       RESERVOIR         // Reservoir sampling
   }
   ```
   
   #### 2.1.3 Extend AlgorithmRuntimeContext
   
   Add new methods to the `AlgorithmRuntimeContext` interface:
   
   ```java
   /**
    * Load edge iterator with sampling configuration
    */
   CloseableIterator<RowEdge> loadEdgesWithSampling(
       EdgeDirection direction, 
       IGNNSamplingConfig samplingConfig
   );
   
   /**
    * Load K-layer neighbor subgraph
    */
   GNNSubgraph loadKLayerSubgraph(
       IGNNSamplingConfig samplingConfig
   );
   ```
   
   ### 2.2 Random Sampling Filter Implementation
   
   #### 2.2.1 RandomSamplingEdgeLimit
   
   Extend the existing `IEdgeLimit` interface:
   
   ```java
   public class RandomSamplingEdgeLimit implements IEdgeLimit {
       private final long sampleSize;
       private final long totalEdgeCount;
       private final Random random;
       private final EdgeDirection direction;
       
       @Override
       public long inEdgeLimit() {
           return direction == EdgeDirection.IN ? sampleSize : Long.MAX_VALUE;
       }
       
       @Override
       public long outEdgeLimit() {
           return direction == EdgeDirection.OUT ? sampleSize : Long.MAX_VALUE;
       }
       
       @Override
       public LimitType limitType() {
           return LimitType.COMPOSED;
       }
   }
   ```
   
   #### 2.2.2 RandomSamplingFilter
   
   Implement random sampling filter based on reservoir algorithm:
   
   ```java
   public class RandomSamplingFilter extends BaseGraphFilter {
       private final IGraphFilter baseFilter;
       private final int sampleSize;
       private final Random random;
       private List<IEdge> reservoir;
       private int edgeCount;
       
       @Override
       public boolean filterEdge(IEdge edge) {
           if (!baseFilter.filterEdge(edge)) {
               return false;
           }
           
           // Reservoir sampling algorithm
           if (edgeCount < sampleSize) {
               reservoir.add(edge);
               edgeCount++;
               return true;
           } else {
               int replaceIndex = random.nextInt(edgeCount + 1);
               if (replaceIndex < sampleSize) {
                   reservoir.set(replaceIndex, edge);
               }
               edgeCount++;
               return false; // Return via reservoir later
           }
       }
   }
   ```
   
   ### 2.3 Layered Sampling Iterator
   
   #### 2.3.1 LayeredSamplingIterator
   
   ```java
   public class LayeredSamplingIterator<K, EV> 
       implements CloseableIterator<LayeredEdges<K, EV>> {
       
       private final GraphState<K, ?, EV> graphState;
       private final IGNNSamplingConfig config;
       private final Queue<LayerTask<K>> taskQueue;
       private final Map<K, Integer> visitedVertices; // Track visit layer
       
       private int currentLayer = 0;
       
       @Override
       public boolean hasNext() {
           return !taskQueue.isEmpty() && currentLayer < config.getSamplingDepth();
       }
       
       @Override
       public LayeredEdges<K, EV> next() {
           List<IEdge<K, EV>> edges = new ArrayList<>();
           int layerSampleSize = config.getLayerSampleSizes().get(currentLayer);
           
           while (!taskQueue.isEmpty()) {
               LayerTask<K> task = taskQueue.poll();
               
               // Configure sampling using StatePushDown
               StatePushDown pushDown = StatePushDown.of()
                   .withEdgeLimit(new RandomSamplingEdgeLimit(
                       layerSampleSize, 
                       config.getDirection()
                   ));
                   
               CloseableIterator<IEdge<K, EV>> edgeIter = 
                   graphState.staticGraph().getEdgeIterator(
                       Collections.singletonList(task.vertexId), 
                       pushDown
                   );
                   
               // Sample edges and add target vertices to next layer queue
               while (edgeIter.hasNext()) {
                   IEdge<K, EV> edge = edgeIter.next();
                   edges.add(edge);
                   
                   K targetId = edge.getTargetId();
                   if (!visitedVertices.containsKey(targetId)) {
                       taskQueue.offer(new LayerTask<>(targetId, currentLayer + 1));
                       visitedVertices.put(targetId, currentLayer + 1);
                   }
               }
           }
           
           currentLayer++;
           return new LayeredEdges<>(currentLayer - 1, edges);
       }
   }
   ```
   
   ### 2.4 GNN Subgraph Data Structure
   
   #### 2.4.1 GNNSubgraph
   
   ```java
   public class GNNSubgraph<K, VV, EV> implements Serializable {
       
       private final K rootVertexId;
       private final Map<Integer, Set<K>> layeredVertices; // Layer -> vertex set
       private final Map<Integer, List<IEdge<K, EV>>> layeredEdges; // Layer -> edge list
       private final Map<K, IVertex<K, VV>> vertexMap; // Vertex properties
       
       public int getDepth() {
           return layeredVertices.size();
       }
       
       public Set<K> getVerticesAtLayer(int layer) {
           return layeredVertices.getOrDefault(layer, Collections.emptySet());
       }
       
       public List<IEdge<K, EV>> getEdgesAtLayer(int layer) {
           return layeredEdges.getOrDefault(layer, Collections.emptyList());
       }
       
       public IVertex<K, VV> getVertex(K vertexId) {
           return vertexMap.get(vertexId);
       }
   }
   ```
   
   
   ## III. Usage Examples
   
   ### 3.1 Basic GNN Sampling UDF
   
   ```java
   @Description(name = "gnn_sampling", description = "GNN subgraph sampling")
   public class GNNSamplingAlgorithm implements AlgorithmUserFunction<Object, GNNMessage> {
       
       private AlgorithmRuntimeContext<Object, GNNMessage> context;
       private IGNNSamplingConfig samplingConfig;
       
       @Override
       public void init(AlgorithmRuntimeContext<Object, GNNMessage> context, Object[] params) {
           this.context = context;
           
           // Configuration: sample 10 neighbors per layer, depth of 2
           this.samplingConfig = GNNSamplingConfig.builder()
               .layerSampleSizes(Arrays.asList(10, 10))
               .samplingDepth(2)
               .strategy(SamplingStrategy.RANDOM)
               .direction(EdgeDirection.OUT)
               .includeVertexProperties(true)
               .includeEdgeProperties(true)
               .build();
       }
       
       @Override
       public void process(RowVertex vertex, Optional<Row> updatedValues, 
                          Iterator<GNNMessage> messages) {
           if (context.getCurrentIterationId() == 1L) {
               // First iteration: load K-layer subgraph
               GNNSubgraph subgraph = context.loadKLayerSubgraph(samplingConfig);
               
               // Process subgraph, send to GNN training component
               context.take(ObjectRow.create(
                   vertex.getId(), 
                   subgraph.serialize()
               ));
           }
       }
       
       @Override
       public void finish(RowVertex vertex, Optional<Row> newValue) {
           // Cleanup resources
       }
       
       @Override
       public StructType getOutputType(GraphSchema graphSchema) {
           return new StructType(
               new TableField("vertex_id", graphSchema.getIdType(), false),
               new TableField("subgraph", StringType.INSTANCE, false)
           );
       }
   }
   ```
   
   ### 3.2 Progressive Sampling Using Iterator
   
   ```java
   @Override
   public void process(RowVertex vertex, Optional<Row> updatedValues, 
                      Iterator<GNNMessage> messages) {
       
       // Load layer by layer using iterator to control memory usage
       for (int layer = 0; layer < samplingConfig.getSamplingDepth(); layer++) {
           
           // Current layer sample size
           int sampleSize = samplingConfig.getLayerSampleSizes().get(layer);
           
           // Load edges using iterator
           CloseableIterator<RowEdge> edgeIter = context.loadEdgesWithSampling(
               EdgeDirection.OUT,
               samplingConfig.withLayerSampleSize(sampleSize)
           );
           
           List<RowEdge> sampledEdges = new ArrayList<>();
           int count = 0;
           
           // Control load count in UDF
           while (edgeIter.hasNext() && count < sampleSize) {
               sampledEdges.add(edgeIter.next());
               count++;
           }
           
           // Process current layer sampling results
           processLayerEdges(layer, sampledEdges);
           
           edgeIter.close();
       }
   }
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@geaflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@geaflow.apache.org
For additional commands, e-mail: issues-help@geaflow.apache.org