candle-graph 0.10.0

TensorFlow Profiler-style execution graphs for candle-rs (trace-only)
Documentation
/* candle-graph layout — powered by dagre (MIT, @dagrejs/dagre 3.1) */
window.CGLayout = (function () {
  "use strict";

  var dagreLib = typeof dagre !== "undefined" ? dagre : (typeof window !== "undefined" ? window.dagre : null);

  function prettyTensorLabel(name) {
    name = String(name || "");
    if (name.indexOf("result_of_") === 0) name = name.slice(10);
    if (name.slice(-9) === "::forward") name = name.slice(0, -9);
    var dot = name.lastIndexOf(".");
    if (dot >= 0) name = name.slice(dot + 1);
    return name.replace(/_/g, " ").trim() || "tensor";
  }

  function labelOf(n) {
    var kind = n.kind || "tensor";
    if (n.short_label) return String(n.short_label);
    var raw = n.label || n.key || n._id || "";
    if (kind === "tensor" || kind === "parameter") return prettyTensorLabel(raw);
    return raw;
  }

  function sublabelOf(n) {
    if (n.kind === "tensor") return [Array.isArray(n.shape) ? n.shape.join(" × ") : n.shape, n.dtype].filter(Boolean).join(" · ");
    function duration(ns) {
      if (ns == null) return "unknown";
      if (ns > 0 && ns < 1000) return ns + " ns";
      if (ns > 0 && ns < 1e6) return (ns / 1000).toFixed(2) + " µs";
      return (ns / 1e6).toFixed(2) + " ms";
    }
    return duration(n.host_self_time_ns) + " self · " + duration(n.host_total_time_ns) + " total";
  }

  function textWidth(text, charW) {
    return Math.max(0, String(text || "").length) * charW;
  }

  function wrapLines(text, maxChars) {
    text = String(text || "");
    var lines = [];
    while (text.length > maxChars && lines.length < 2) {
      var chunk = text.slice(0,maxChars);
      var boundaries = Array.from(chunk.matchAll(/::|[./_ ]/g));
      var last = boundaries.length ? boundaries[boundaries.length-1] : null;
      var cut = last && last.index > maxChars / 2 ? last.index + last[0].length : maxChars;
      lines.push(text.slice(0,cut));
      text = text.slice(cut);
    }
    lines.push(text.length > maxChars ? text.slice(0,maxChars-1) + "" : text);
    return lines;
  }

  function nodeDims(n) {
    var label = n.label || labelOf(n);
    var lines = wrapLines(label, 28).flatMap(function (line) {
      return line.match(/.{1,28}/g) || [line];
    });
    if (lines.length > 3) lines = lines.slice(0,2).concat(lines[2].slice(0,27) + "");
    n._titleLines = lines;
    n._sub = sublabelOf(n);
    n._subLines = n._sub ? n._sub.split(" · ") : [];
    var textW = Math.max.apply(null, lines.map(function (line) { return textWidth(line,7.5); }).concat(n._subLines.map(function (line) { return textWidth(line,6.4); })));
    return { w: Math.ceil(Math.max(220, Math.min(320,textW + 32))), h: 56 + (lines.length + n._subLines.length) * 18 };
  }

  function pointsToPath(points) {
    if (!points || points.length < 2) return "";
    var d = "M" + points[0].x + " " + points[0].y;
    for (var i = 1; i < points.length; i++) {
      d += " L" + points[i].x + " " + points[i].y;
    }
    return d;
  }

  function edgeKey(e) {
    return { v: e._from, w: e._to, name: e._key };
  }

  function fallbackLayout(nodes, direction) {
    var LR = direction !== "TB";
    var gapX = LR ? 260 : 48;
    var gapY = LR ? 52 : 100;
    nodes.forEach(function (n, i) {
      var sz = nodeDims(n, "layered");
      n._w = sz.w;
      n._h = sz.h;
      if (LR) {
        n._x = (i % 8) * gapX;
        n._y = Math.floor(i / 8) * gapY;
      } else {
        n._x = (i % 6) * gapX;
        n._y = Math.floor(i / 6) * gapY;
      }
      n._cx = n._x + n._w / 2;
      n._cy = n._y + n._h / 2;
    });
  }

  function layoutWithDagre(nodes, edges, opts) {
    if (!dagreLib || !dagreLib.graphlib || !dagreLib.layout) {
      fallbackLayout(nodes, opts.direction || "LR");
      return;
    }

    var g = new dagreLib.graphlib.Graph({ multigraph: true, compound: false });
    var rankdir = opts.direction === "TB" ? "TB" : "LR";
    var layoutKind = opts.layout || "layered";

    g.setGraph({
      rankdir: rankdir,
      ranker: "network-simplex",
      acyclicer: "greedy",
      align: "UL",
      nodesep: opts.nodesep || 80,
      edgesep: opts.edgesep || 64,
      ranksep: opts.ranksep || 200,
      marginx: 56,
      marginy: 56,
    });
    g.setDefaultEdgeLabel(function () { return {}; });

    nodes.forEach(function (n) {
      var sz = nodeDims(n, layoutKind);
      g.setNode(n._id, { width: sz.w, height: sz.h });
    });

    edges.forEach(function (e, i) {
      if (!g.hasNode(e._from) || !g.hasNode(e._to)) return;
      e._key = "e" + i;
      g.setEdge(
        { v: e._from, w: e._to, name: e._key },
        { minlen: 1, weight: 1 }
      );
    });

    try {
      dagreLib.layout(g);
    } catch (err) {
      console.error("dagre layout failed:", err);
      fallbackLayout(nodes, opts.direction || "LR");
      return;
    }

    nodes.forEach(function (n) {
      var d = g.node(n._id);
      if (!d) return;
      n._w = d.width;
      n._h = d.height;
      n._x = d.x - d.width / 2;
      n._y = d.y - d.height / 2;
      n._cx = d.x;
      n._cy = d.y;
      n._col = d.rank;
      n._layer = d.rank;
    });

    edges.forEach(function (e) {
      if (!e._key) return;
      var edgeData = g.edge(edgeKey(e));
      e._points = edgeData && edgeData.points ? edgeData.points : null;
    });
  }

  function layoutLayered(nodes, edges, direction) {
    layoutWithDagre(nodes, edges, {
      layout: "layered",
      direction: direction || "LR",
      nodesep: 76,
      edgesep: 68,
      ranksep: 210,
    });
  }

  function layoutTree(nodes, edges) {
    layoutWithDagre(nodes, edges, {
      layout: "tree",
      direction: "LR",
      nodesep: 64,
      edgesep: 56,
      ranksep: 220,
    });
  }

  function layerBands(nodes, direction) {
    var LR = direction !== "TB";
    var byLayer = {};
    nodes.forEach(function (n) {
      if (!Number.isFinite(n._layer)) return;
      var L = n._layer;
      if (!byLayer[L]) {
        byLayer[L] = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity, layer: L };
      }
      var b = byLayer[L];
      b.minX = Math.min(b.minX, n._x);
      b.maxX = Math.max(b.maxX, n._x + n._w);
      b.minY = Math.min(b.minY, n._y);
      b.maxY = Math.max(b.maxY, n._y + n._h);
    });
    var pad = 20;
    return Object.keys(byLayer).map(function (k) {
      var b = byLayer[k];
      return {
        x: b.minX - pad,
        y: b.minY - pad,
        w: b.maxX - b.minX + pad * 2,
        h: b.maxY - b.minY + pad * 2,
      };
    });
  }

  /** Anchor on node boundary (not center) for cleaner orthogonal routing. */
  function portPoint(node, side, along) {
    var pad = 4;
    var cx = node._cx;
    var cy = node._cy;
    if (along != null) {
      if (side === "east" || side === "west") cy = along;
      else cx = along;
    }
    switch (side) {
      case "east":
        return { x: node._x + node._w + pad, y: cy };
      case "west":
        return { x: node._x - pad, y: cy };
      case "south":
        return { x: cx, y: node._y + node._h + pad };
      case "north":
        return { x: cx, y: node._y - pad };
      default:
        return { x: node._cx, y: node._cy };
    }
  }

  /** Spread attachment points along a node face so parallel edges do not overlap. */
  function spreadPort(node, side, index, total) {
    var margin = 10;
    var span = side === "east" || side === "west" ? node._h : node._w;
    var usable = Math.max(span - margin * 2, 8);
    var t = (index + 1) / (total + 1);
    var along = (side === "east" || side === "west" ? node._y : node._x) + margin + t * usable;
    return portPoint(node, side, along);
  }

  function choosePortSides(from, to, direction) {
    var LR = direction !== "TB";
    if (LR) {
      if (from._layer != null && to._layer != null && from._layer === to._layer) {
        var dy = to._cy - from._cy;
        if (dy >= 0) return { from: "south", to: "north" };
        return { from: "north", to: "south" };
      }
      return { from: "east", to: "west" };
    }
    if (from._layer != null && to._layer != null && from._layer === to._layer) {
      var dx = to._cx - from._cx;
      if (dx >= 0) return { from: "east", to: "west" };
      return { from: "west", to: "east" };
    }
    return { from: "south", to: "north" };
  }

  function orthogonalRoute(fromPt, toPt, direction, laneOffset) {
    var LR = direction !== "TB";
    var offset = laneOffset || 0;
    var pts = [fromPt];
    if (LR) {
      var midX = (fromPt.x + toPt.x) / 2 + offset;
      if (Math.abs(fromPt.y - toPt.y) < 2) {
        pts.push({ x: toPt.x, y: toPt.y });
      } else {
        pts.push({ x: midX, y: fromPt.y });
        pts.push({ x: midX, y: toPt.y });
        pts.push({ x: toPt.x, y: toPt.y });
      }
    } else {
      var midY = (fromPt.y + toPt.y) / 2 + offset;
      if (Math.abs(fromPt.x - toPt.x) < 2) {
        pts.push({ x: toPt.x, y: toPt.y });
      } else {
        pts.push({ x: fromPt.x, y: midY });
        pts.push({ x: toPt.x, y: midY });
        pts.push({ x: toPt.x, y: toPt.y });
      }
    }
    return pts;
  }

  function laneKeyForEdge(from, to, direction) {
    var LR = direction !== "TB";
    if (LR) {
      var lo = Math.min(from._layer != null ? from._layer : 0, to._layer != null ? to._layer : 0);
      var hi = Math.max(from._layer != null ? from._layer : 0, to._layer != null ? to._layer : 0);
      return "L" + lo + "-" + hi + ":" + Math.round(from._cy) + ">" + Math.round(to._cy);
    }
    var loX = Math.min(from._layer != null ? from._layer : 0, to._layer != null ? to._layer : 0);
    var hiX = Math.max(from._layer != null ? from._layer : 0, to._layer != null ? to._layer : 0);
    return "T" + loX + "-" + hiX + ":" + Math.round(from._cx) + ">" + Math.round(to._cx);
  }

  function assignEdgePorts(nodes, edges, byId, layout, direction) {
    var dir = direction || "LR";
    var fromGroups = {};
    var toGroups = {};
    var laneGroups = {};

    edges.forEach(function (e, i) {
      var from = byId[e._from];
      var to = byId[e._to];
      if (!from || !to) return;
      var sides = choosePortSides(from, to, dir);
      e._fromSide = sides.from;
      e._toSide = sides.to;
      var fk = e._from + ":" + sides.from;
      var tk = e._to + ":" + sides.to;
      if (!fromGroups[fk]) fromGroups[fk] = [];
      if (!toGroups[tk]) toGroups[tk] = [];
      fromGroups[fk].push(i);
      toGroups[tk].push(i);
    });

    edges.forEach(function (e, i) {
      var from = byId[e._from];
      var to = byId[e._to];
      if (!from || !to || !e._fromSide) return;

      if (e._points && e._points.length >= 2) {
        e._path = pointsToPath(e._points);
        return;
      }

      var fk = e._from + ":" + e._fromSide;
      var tk = e._to + ":" + e._toSide;
      var fi = fromGroups[fk].indexOf(i);
      var ft = fromGroups[fk].length;
      var ti = toGroups[tk].indexOf(i);
      var tt = toGroups[tk].length;

      e._fromPort = spreadPort(from, e._fromSide, fi, ft);
      e._toPort = spreadPort(to, e._toSide, ti, tt);

      var laneKey = laneKeyForEdge(from, to, dir);
      if (!laneGroups[laneKey]) laneGroups[laneKey] = [];
      laneGroups[laneKey].push(i);
    });

    Object.keys(laneGroups).forEach(function (key) {
      var group = laneGroups[key];
      var laneStep = 16;
      group.forEach(function (edgeIdx, laneIdx) {
        var e = edges[edgeIdx];
        var offset = (laneIdx - (group.length - 1) / 2) * laneStep;
        e._path = pointsToPath(orthogonalRoute(e._fromPort, e._toPort, dir, offset));
      });
    });
  }

  function routeEdge(e) {
    if (e._path) return e._path;
    if (e._points && e._points.length >= 2) return pointsToPath(e._points);
    return "";
  }

  function edgeMidpoint(e) {
    var pts = e._points;
    if (!pts || pts.length === 0) return null;
    var p = pts[Math.floor(pts.length / 2)];
    return { x: p.x, y: p.y - 4 };
  }

  function edgeClass(e) {
    if ((e.kind || "").indexOf("sever") >= 0 || e.grad_state === "Severed") return "edge-severed";
    if (e.kind === "call") return "edge-call";
    if (e.label && e.label.indexOf("ms") >= 0) return "edge-timed";
    if (e.label === "in") return "edge-in";
    if (e.label === "out") return "edge-out";
    if (e.kind === "composition") return "edge-composition";
    if (e.kind === "depends_on" || e.kind === "sequence") return "edge-pipeline";
    return "edge-default";
  }

  return {
    nodeDims: nodeDims,
    labelOf: labelOf,
    sublabelOf: sublabelOf,
    layoutLayered: layoutLayered,
    layoutTree: layoutTree,
    layerBands: layerBands,
    assignEdgePorts: assignEdgePorts,
    routeEdge: routeEdge,
    edgeMidpoint: edgeMidpoint,
    edgeClass: edgeClass,
  };
})();