code: public static long getOptimalBufferSize(long paramsLength, int numWorkers, int queueSize) {
          // we add 64kb just for future proof volatility
          val bufferSize = ((paramsLength / 16) + 65536) * numWorkers * queueSize * 4;
          return bufferSize;
      }
docs: This method returns optimal bufferSize for a given model
  
  We know, that updates are guaranteed to have MAX size of params / 16. So, here we go.
  I.e. for model with 100m params, that's 400m of floats (or 800m of doubles)
  The worst case for us is bitmap encoding, that takes 2 bits to encode each gradient value
  
  so, for float in worst case we'll have (100m / 16) int elements. So, our buffer size will be 6.25m * queueSize * 4 bytes per int
  
  @param paramsLength
  @param numWorkers
  @param queueSize
  @return