Directed Acyclic Graph

2 posts

slack3 min readCurated summary

Build better software to build software better

Slack’s backend build pipeline for Quip and Slack Canvas once took 60 minutes, delaying feedback and slowing delivery. The team improved build performance by applying familiar software-engineering techniques—caching, parallelization, precise interfaces, and careful decomposition—using Bazel. The central argument is that build systems should be designed like high-performance programs: do less work, distribute unavoidable work, and define work units rigorously. ## Modeling Builds as Dependency Graphs - Applications can be represented as directed acyclic graphs of source files, intermediate artifacts, and deployable outputs. - A backend artifact depends on Python files, while a frontend artifact depends on TypeScript files. - Changing a Python file should rebuild the backend but not unrelated frontend components. - Clearly defined graph nodes allow build systems to optimize work rather than rebuilding everything. ## Caching and Hermetic Work - Caching avoids repeating expensive operations by storing outputs for known inputs. - The article uses a cached recursive `factorial()` function as an analogy: - The input is the cache key. - The return value is the cached artifact. - Effective caching requires work to be: - **Hermetic:** dependent only on explicitly provided inputs. - **Idempotent:** producing the same output for the same inputs. - Cache hit rate matters: poorly defined work units produce more cache misses. ## Granular Cache Units - Caching an entire `process_images(images, transforms)` operation is inefficient because changing one image invalidates the result for every image. - A more granular design caches `process_image(image, transform)` independently. - The higher-level operation can then reuse cached results and process only new image-transform combinations. - Smaller, well-defined units generally improve cache reuse and reduce rebuild time. ## Parallelizing Independent Work - Image processing can also be distributed across CPU threads using `ThreadPoolExecutor`. - Parallel work requires: - Completely specified inputs and outputs. - The ability to transfer data across thread, process, or network boundaries. - Handling completion and failure in any order. - APIs must document ordering guarantees; the threaded example returns images in completion order rather than input order. - Work-unit granularity affects scalability: - Too few large tasks limit available parallelism. - Too many tiny tasks may introduce coordination overhead. - The appropriate balance depends on the workload. ## Applying These Principles to Bazel - Bazel represents builds as directed acyclic graphs made of targets. - Each target defines: - Its input or dependency files. - Its output files. - The commands that transform inputs into outputs. - This structure provides the foundation for caching and parallel execution, just as explicit function inputs and outputs enable those optimizations in application code. The practical recommendation is to design build steps as small, hermetic, idempotent, and independently executable units. Combined with Bazel’s dependency graph, this lets teams avoid unnecessary work, maximize cache hits, and run independent tasks concurrently—turning slow build pipelines into faster sources of developer feedback.

Read original(opens in new tab)
googleOriginal article

InstructPipe: Generating Visual Blocks pipelines with human instructions and LLMs (opens in new tab)

InstructPipe is a research prototype designed to simplify machine learning prototyping by generating visual programming pipelines directly from natural language instructions. By leveraging a multi-stage large language model (LLM) framework, the system automates the selection and connection of nodes to lower the barrier for novice users. The result is a streamlined workflow that transforms abstract text commands into functional, editable node-graph diagrams within the Visual Blocks for ML environment. ### Pipeline Representation and Efficiency - Visual Blocks pipelines are structured as Directed Acyclic Graphs (DAGs) and are typically stored in a verbose JSON format. - To improve LLM performance, InstructPipe utilizes a "pseudocode" intermediate representation that is highly token-efficient, compressing pipeline data from 2.8k tokens down to approximately 123 tokens. - This pseudocode defines output variables, unique node IDs, and node types while specifying arguments such as input images or text prompts (e.g., `pali_1_out:pali(image=input_image_1, prompt=input_text_1)`). ### Two-Stage LLM Refinement - The **Node Selector** module acts as a high-level filter, using brief node descriptions to identify a relevant subset of tools from the library based on the user's intent. - The **Code Writer** module receives the filtered list and uses detailed node configurations—including specific input/output data types and usage examples—to draft the actual pipeline logic. - This dual-prompting strategy mimics human developer behavior by first scanning documentation categories and then focusing on specific function requirements to ensure accurate node connections. ### Interpretation and Execution - A dedicated **Code Interpreter** parses the generated pseudocode to reconstruct the final JSON-formatted pipeline required by the visual editor. - The system renders the resulting graph in an interactive workspace, allowing users to immediately execute, modify, or extend the machine learning workflow. - Technical evaluations indicate that this approach effectively supports multimodal pipelines, such as those involving the PaLI model for vision-language tasks, while significantly reducing the learning curve for new users. InstructPipe demonstrates how LLMs can bridge the gap between high-level human intent and low-code visual programming environments. For developers and researchers, this approach mitigates the "blank canvas" problem, allowing for faster experimentation and the rapid prototyping of complex machine learning architectures through simple text-based collaboration.