2017-12-13 129 views
11

Tôi có một máy chủ có nhiều GPU và muốn tận dụng chúng trong khi suy luận mô hình bên trong ứng dụng java. Theo mặc định, lưu lượng sẽ thu thập tất cả các GPU hiện có, nhưng chỉ sử dụng GPU đầu tiên.Suy luận Multi-GPU của dòng chảy Tensor

tôi có thể nghĩ ra ba lựa chọn để vượt qua vấn đề này:

  1. Giới hạn tầm nhìn thiết bị trên mức độ xử lý, cụ thể là sử dụng CUDA_VISIBLE_DEVICES biến môi trường.

    Điều đó sẽ yêu cầu tôi chạy một số phiên bản của ứng dụng java và phân phối lưu lượng truy cập trong số đó. Không phải là ý tưởng hấp dẫn đó.

  2. Launch nhiều phiên bên trong một ứng dụng duy nhất và cố gắng gán một thiết bị cho mỗi trong số họ qua ConfigProto:

    public class DistributedPredictor { 
    
        private Predictor[] nested; 
        private int[] counters; 
    
        // ... 
    
        public DistributedPredictor(String modelPath, int numDevices, int numThreadsPerDevice) { 
         nested = new Predictor[numDevices]; 
         counters = new int[numDevices]; 
    
         for (int i = 0; i < nested.length; i++) { 
          nested[i] = new Predictor(modelPath, i, numDevices, numThreadsPerDevice); 
         } 
        } 
    
        public Prediction predict(Data data) { 
         int i = acquirePredictorIndex(); 
         Prediction result = nested[i].predict(data); 
         releasePredictorIndex(i); 
         return result; 
        } 
    
        private synchronized int acquirePredictorIndex() { 
         int i = argmin(counters); 
         counters[i] += 1; 
         return i; 
        } 
    
        private synchronized void releasePredictorIndex(int i) { 
         counters[i] -= 1; 
        } 
    } 
    
    
    public class Predictor { 
    
        private Session session; 
    
        public Predictor(String modelPath, int deviceIdx, int numDevices, int numThreadsPerDevice) { 
    
         GPUOptions gpuOptions = GPUOptions.newBuilder() 
           .setVisibleDeviceList("" + deviceIdx) 
           .setAllowGrowth(true) 
           .build(); 
    
         ConfigProto config = ConfigProto.newBuilder() 
           .setGpuOptions(gpuOptions) 
           .setInterOpParallelismThreads(numDevices * numThreadsPerDevice) 
           .build(); 
    
         byte[] graphDef = Files.readAllBytes(Paths.get(modelPath)); 
         Graph graph = new Graph(); 
         graph.importGraphDef(graphDef); 
    
         this.session = new Session(graph, config.toByteArray()); 
        } 
    
        public Prediction predict(Data data) { 
         // ... 
        } 
    } 
    

    Cách tiếp cận này dường như làm việc tốt trong nháy mắt. Tuy nhiên, các phiên thỉnh thoảng bỏ qua tùy chọn setVisibleDeviceList và tất cả đều chuyển sang thiết bị đầu tiên gây ra lỗi ngoài bộ nhớ.

  3. Tạo mô hình theo kiểu đa tháp ở trăn sử dụng đặc điểm tf.device(). Về phía java, hãy cung cấp các tòa tháp khác nhau của Predictor trong một phiên được chia sẻ.

    Cảm thấy rườm rà và vô lý đối với tôi.

UPDATE: Như @ash đề xuất, có thêm một lựa chọn:

  1. Gán một thiết bị phù hợp với từng hoạt động của đồ thị hiện bằng cách sửa đổi định nghĩa của nó (graphDef).

    Để làm cho nó thực hiện, người ta có thể thích ứng với các mã từ Cách 2:

    public class Predictor { 
    
        private Session session; 
    
        public Predictor(String modelPath, int deviceIdx, int numDevices, int numThreadsPerDevice) { 
    
         byte[] graphDef = Files.readAllBytes(Paths.get(modelPath)); 
         graphDef = setGraphDefDevice(graphDef, deviceIdx) 
    
         Graph graph = new Graph(); 
         graph.importGraphDef(graphDef); 
    
         ConfigProto config = ConfigProto.newBuilder() 
           .setAllowSoftPlacement(true) 
           .build(); 
    
         this.session = new Session(graph, config.toByteArray()); 
        } 
    
        private static byte[] setGraphDefDevice(byte[] graphDef, int deviceIdx) throws InvalidProtocolBufferException { 
         String deviceString = String.format("/gpu:%d", deviceIdx); 
    
         GraphDef.Builder builder = GraphDef.parseFrom(graphDef).toBuilder(); 
         for (int i = 0; i < builder.getNodeCount(); i++) { 
          builder.getNodeBuilder(i).setDevice(deviceString); 
         } 
         return builder.build().toByteArray(); 
        } 
    
        public Prediction predict(Data data) { 
         // ... 
        } 
    } 
    

    Cũng giống như cách tiếp cận nêu trên khác, một trong những điều này không giải phóng tôi khỏi tay phân phối dữ liệu giữa các thiết bị. Nhưng ít nhất nó hoạt động ổn định và tương đối dễ thực hiện. Nhìn chung, điều này trông giống như một kỹ thuật (gần như) bình thường.

Có cách nào thanh lịch để thực hiện điều cơ bản như vậy với API java lưu lượng không? Có những câu chuyện mới trên trang chủ.

Trả lời

6

Tóm lại: Có một giải pháp thay thế, nơi bạn kết thúc với một phiên trên mỗi GPU.

Chi tiết:

Luồng chung là thời gian TensorFlow tôn trọng các thiết bị được chỉ định cho hoạt động trong biểu đồ. Nếu không có thiết bị được chỉ định cho một hoạt động, sau đó nó "đặt" nó dựa trên một số chẩn đoán. Những chẩn đoán hiện tại dẫn đến "hoạt động diễn ra trên GPU: 0 nếu có GPU và có hạt nhân GPU cho hoạt động" (Placer::Run trong trường hợp bạn quan tâm).

Những gì bạn yêu cầu tôi nghĩ là yêu cầu tính năng hợp lý cho TensorFlow - khả năng xử lý các thiết bị trong biểu đồ được nối tiếp dưới dạng đồ thị "ảo" được ánh xạ tới một bộ thiết bị "phyiscal" trong thời gian chạy hoặc "thiết bị mặc định". Tính năng này hiện không tồn tại. Thêm tùy chọn như vậy vào ConfigProto là một thứ bạn có thể muốn gửi yêu cầu tính năng.

Tôi có thể đề xuất giải pháp thay thế tạm thời. Đầu tiên, một số bình luận về các giải pháp được đề xuất của bạn.

  1. Ý tưởng đầu tiên của bạn chắc chắn sẽ hoạt động, nhưng như bạn đã chỉ ra, rất cồng kềnh.

  2. Đặt sử dụng visible_device_list trong ConfigProto không hoàn toàn hoạt động vì đây thực sự là cài đặt cho mỗi quá trình và bị bỏ qua sau phiên đầu tiên được tạo trong quá trình. Điều này chắc chắn không phải là tài liệu cũng như nó nên (và hơi tiếc là điều này xuất hiện trong cấu hình cho mỗi phiên). Tuy nhiên, điều này giải thích tại sao đề xuất của bạn ở đây không hiệu quả và tại sao bạn vẫn thấy một GPU được sử dụng.

  3. Điều này có thể hoạt động.

Một tùy chọn khác là kết thúc với các biểu đồ khác nhau (với các thao tác được đặt rõ ràng trên các GPU khác nhau), dẫn đến một phiên trên mỗi GPU. Một cái gì đó như thế này có thể được sử dụng để chỉnh sửa đồ thị và rõ ràng gán một thiết bị cho mỗi hoạt động:

public static byte[] modifyGraphDef(byte[] graphDef, String device) throws Exception { 
    GraphDef.Builder builder = GraphDef.parseFrom(graphDef).toBuilder(); 
    for (int i = 0; i < builder.getNodeCount(); ++i) { 
    builder.getNodeBuilder(i).setDevice(device); 
    } 
    return builder.build().toByteArray(); 
} 

Sau đó bạn có thể tạo một GraphSession mỗi GPU sử dụng một cái gì đó như:

final int NUM_GPUS = 8; 
// setAllowSoftPlacement: Just in case our device modifications were too aggressive 
// (e.g., setting a GPU device on an operation that only has CPU kernels) 
// setLogDevicePlacment: So we can see what happens. 
byte[] config = 
    ConfigProto.newBuilder() 
     .setLogDevicePlacement(true) 
     .setAllowSoftPlacement(true) 
     .build() 
     .toByteArray(); 
Graph graphs[] = new Graph[NUM_GPUS]; 
Session sessions[] = new Session[NUM_GPUS]; 
for (int i = 0; i < NUM_GPUS; ++i) { 
    graphs[i] = new Graph(); 
    graphs[i].importGraphDef(modifyGraphDef(graphDef, String.format("/gpu:%d", i))); 
    sessions[i] = new Session(graphs[i], config);  
} 

Sau đó sử dụng sessions[i] để thực thi biểu đồ trên GPU #i.

Hy vọng điều đó sẽ hữu ích.