docs: public static SameDiff fromFlatBuffers(ByteBuffer bbIn) throws IOException {
  
          FlatGraph fg = FlatGraph.getRootAsFlatGraph(bbIn);
  
          int numOps = fg.nodesLength();
          int numVars = fg.variablesLength();
          List<FlatNode> ops = new ArrayList<>(numOps);
          for( int i=0; i<numOps; i++ ){
              ops.add(fg.nodes(i));
          }
          List<FlatVariable> vars = new ArrayList<>(numVars);
          for( int i = 0; i < numVars; i++) {
              vars.add(fg.variables(i));
          }
  
          FlatConfiguration conf = fg.configuration();
  
          /* Reconstruct the graph
          We'll do the reconstruction manually here, rather than using sd.var(...), so that we have more control
          over the final result.
           */
  
          SameDiff sd = SameDiff.create();
  
          //Reconstruct placeholders
          int numPlaceholders = fg.placeholdersLength();
          Set<String> ph = new LinkedHashSet<>();
          for(int i=0; i<numPlaceholders; i++ ){
              ph.add(fg.placeholders(i));
          }
  
          //Reconstruct variables:
          Map<Integer,SDVariable> varNodeIds = new HashMap<>();
          Map<Pair<Integer,Integer>, SDVariable> variablesByNodeAndOutNum = new HashMap<>();
          Map<String,List<SDVariable>> variablesByName = new HashMap<>();
          for(FlatVariable v : vars){
              int shapeLength = v.shapeLength();
              long[] shape = new long[shapeLength];
              for( int i = 0; i < shapeLength; i++) {
                  shape[i] = v.shape(i);
              }
  
              String n = v.name();
  
              byte dtypeByte = v.dtype();
              org.nd4j.linalg.api.buffer.DataType dtype = FlatBuffersMapper.getDataTypeFromByte(dtypeByte);
  
              //TODO Infer this properly! Could be constant, etc.
              VariableType vt = VariableType.values()[v.variabletype()];
              SDVariable var = new SDVariable(n, vt, sd, shape, dtype, null);
              sd.variables.put(n, Variable.builder().name(n).variable(var).build());
              sd.variableNameToShape.put(n, shape);
  
  
              FlatArray fa = v.ndarray();
              if(fa != null && vt != VariableType.ARRAY){
                  INDArray arr;
                  try(MemoryWorkspace ws = Nd4j.getWorkspaceManager().scopeOutOfWorkspaces()) {
                      arr = Nd4j.createFromFlatArray(fa);
                  }
                  sd.setArrayForVariable(n, arr);
              }
  
              IntPair id = v.id();    //First value: node (op) id. Second: output number
              variablesByNodeAndOutNum.put(new Pair<>(id.first(), id.second()), var);
  
              if(!variablesByName.containsKey(n)){
                  variablesByName.put(n, new ArrayList<SDVariable>());
              }
  
              List<SDVariable> list = variablesByName.get(n);
              list.add(var);
          }
  
          //Reconstruct ops:
          for(FlatNode fn : ops){
              DifferentialFunction df = FlatBuffersMapper.fromFlatNode(fn);
              String name = fn.name();
              df.setSameDiff(sd);
              df.setOwnName(name);
              if(sd.ops.containsKey(name)){
                  sd.ops.get(name).setOp(df);
              } else {
                  sd.ops.put(name, SameDiffOp.builder().name(name).op(df).build());
              }
  
              int outLength = fn.outputLength();
              int[] outs = new int[outLength];
              for( int i=0; i<outLength; i++ ){
                  outs[i] = fn.output(i);
              }
  
              int opId = fn.id();
  
              //Work out inputs and outputs:
              int[] output = new int[fn.outputLength()];
              for (int i = 0; i < output.length; i++) {
                  output[i] = fn.output(i);
              }
              int[] input = new int[fn.inputLength()];
              for (int i = 0; i < input.length; i++) {
                  input[i] = fn.input(i);
              }
              IntPair[] inputPaired = new IntPair[fn.inputPairedLength()];
              List<Pair<Integer,Integer>> intPairList = new ArrayList<>();
              for (int i = 0; i < inputPaired.length; i++) {
                  inputPaired[i] = fn.inputPaired(i);
                  intPairList.add(new Pair<>(inputPaired[i].first(), inputPaired[i].second()));
              }
  
              String[] inputNames = new String[inputPaired.length];
              for(int i=0; i<inputPaired.length; i++ ){
                  int nodeId = inputPaired[i].first();
                  int nodeOutNum = inputPaired[i].second();
                  SDVariable varIn = variablesByNodeAndOutNum.get(new Pair<>(nodeId, nodeOutNum));
                  if(varIn == null){
                      //The variable corresponding to this op was not
                  }
                  inputNames[i] = varIn.getVarName();
              }
              sd.ops.get(df.getOwnName()).setInputsToOp(Arrays.asList(inputNames));
  
              //Record that input variables are input to this op
              for(String inName : inputNames) {
                  Variable v = sd.getVariables().get(inName);
                  if(v.getInputsForOp() == null){
                      v.setInputsForOp(new ArrayList<String>());
                  }
                  if(!v.getInputsForOp().contains(df.getOwnName())){
                      v.getInputsForOp().add(df.getOwnName());
                  }
              }
  
              List<SDVariable> varsForOp = variablesByName.get(name);
  
              //Can't assume that variables for the op have all been defined. For example, if we export before execution in SameDiff
              //In theory, we can reconstruct the output variables (minus names) if we know the number of op outputs
              //And we can calculate the op outputs - in most cases - after the op has been created and parameters set
              int numOutputs = df.getNumOutputs();
              if(numOutputs <= 0){
                  numOutputs = fn.outputLength();
              }
  
              String[] varNames = null;
              if(varsForOp != null && varsForOp.size() == numOutputs){
                  varNames = new String[varsForOp.size()];
                  for( int i=0; i<varNames.length; i++ ){
                      varNames[i] = varsForOp.get(i).getVarName();
                      sd.getVariables().get(varNames[i]).setOutputOfOp(df.getOwnName());
                  }
                  sd.ops.get(df.getOwnName()).setOutputsOfOp(Arrays.asList(varNames));
              } else {
                  //We're missing some variables...
                  int outputNamesLength = fn.outputNamesLength();
                  varNames = new String[outputNamesLength];
                  for( int i=0; i<outputNamesLength; i++ ){
                      String n = fn.outputNames(i);
                      varNames[i] = n;
                      if(!sd.variables.containsKey(n)){
                          //Need to create the variable - perhaps it wasn't exported. Note output of node -> can only be VARIABLE type
                          SDVariable var = new SDVariable(n, VariableType.VARIABLE, sd, null, null, null);
                          sd.variables.put(n, Variable.builder().name(n).variable(var).build());
                          variablesByNodeAndOutNum.put(new Pair<>(opId, i), var);
                      }
                      sd.getVariables().get(varNames[i]).setOutputOfOp(df.getOwnName());
                  }
                  sd.ops.get(df.getOwnName()).setOutputsOfOp(Arrays.asList(varNames));
              }
  
              //Check the op mapping int he variablesByNodeAndOutputNum
              //For multi-output ops, variables will have their own index, not related to the op index
              for( int i=0; i<varNames.length; i++ ){
                  Pair<Integer,Integer> p = new Pair<>(opId, i);
                  if(!variablesByNodeAndOutNum.containsKey(p)){
                      variablesByNodeAndOutNum.put(p, sd.getVariable(varNames[i]));
                  }
              }
          }
  
          //Reconstruct loss variables
          if(fg.lossVariablesLength() > 0){
              for(int i=0; i<fg.lossVariablesLength(); i++ ){
                  sd.addLossVariable(fg.lossVariables(i));
              }
          }
  
          return sd;
      }