Chapter 1: Networking Fundamentals
Master the networking concepts that power every ML API call, dataset download, and distributed training run.
- Topics
- 8
- Videos
- 3
- Min read
- 66
Chapter Overview
Networking is the invisible infrastructure behind every AI system. Every model training run, every API call, every dataset download, and every distributed computation depends on reliable, performant networking. Yet many ML engineers treat the network as a black box — until something breaks and they're stuck debugging mysterious timeouts, connection refused errors, or painfully slow data transfers.
This chapter builds your networking knowledge from the ground up, starting with how data travels across the internet and ending with the specialized networking requirements of distributed GPU training. You will learn the protocols (TCP, UDP, HTTP) that your ML tools use under the hood, how to secure your model APIs with TLS and firewalls, how to efficiently transfer multi-gigabyte model files, and how to diagnose network bottlenecks that throttle your training pipelines.
By the end of this chapter, you will be able to diagnose why your SSH connection dropped, understand why your model API returns 503 errors, configure firewalls to protect GPU servers, and reason about whether your network bandwidth is sufficient for distributed training. These skills separate productive AI engineers from those who spend hours fighting infrastructure instead of building models.
Chapter Roadmap
Click any topic to jump in
How the Internet Works
Packets, IP addresses, routing, NAT — the substrate every higher-layer protocol stands on.
From routed packets to end-to-end transport
TCP/IP & UDP
Reliable byte streams vs fire-and-forget datagrams — the two transport contracts and when each one matters.
HTTP & HTTPS
The request/response protocol that powers REST APIs, plus TLS encryption that makes the public internet safe.
DNS & Domain Names
How human-readable names resolve to IP addresses, why TTLs matter, and where DNS caching can ruin your day.
Ports & Firewalls
Service endpoints, security groups, iptables — the gatekeepers that decide which traffic actually reaches your GPU box.
Resolution and access control
SSH & Remote Access
Key pairs, SSH config, port forwarding, rsync — the daily tools for working on remote training clusters.
REST APIs & gRPC
How models get served — JSON-over-HTTP for simplicity vs Protocol Buffers and streaming RPCs for performance.
Networking for ML
All-reduce, NCCL, bandwidth hierarchy, pipeline bottlenecks — where the network becomes the bottleneck in distributed training.
Topics
8 topics in this chapter
You start a 40 GB dataset download on a cloud GPU server and it crawls at 3 MB/s, while the same download from another region finishes in minutes. Is the problem your instance, the cloud provider, a router halfway across the world, or the server at the other end? Without a mental model of how data actually moves, every slow transfer or unreachable server is a guess.
This chapter builds that model from the bottom up, and this first topic covers the foundation. The internet is a packet-switched network: data is cut into packets, each labelled with source and destination IP addresses, and routers forward each packet independently toward its destination. Private address ranges and NAT explain why a cloud instance has one address inside its network and another outside it.
The concepts cover IP addressing, packets and the MTU, routing and BGP, the client-server model, NAT, and the difference between latency and throughput. Later topics stack transport (TCP and UDP), HTTP, DNS, ports, SSH, APIs and distributed-training networking on top of these basics.
Definition
The internet is a global packet-switched network of independently operated networks. Hosts are identified by IP addresses, data travels as packets of at most one MTU, routers forward each packet hop by hop using tables built by routing protocols such as BGP, and NAT maps private addresses to shared public ones at network edges.
In this topic
- 1IP Addresses
- 2Packets
- 3Routing
- 4Client-Server Model
- 5NAT (Network Address Translation)
- 6Latency vs Throughput
IP Addresses
Every device on a network has a unique numerical label called an IP address. IPv4 uses 32-bit addresses (giving roughly 4.3 billion unique addresses), written as four octets like 192.168.1.10. IPv6 uses 128-bit addresses to solve the exhaustion problem. Cloud GPU instances typically get private IPs within a VPC and optional public IPs for SSH access.
Private address ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x) are used within local networks and are not routable on the public internet. NAT gateways translate these private addresses to a shared public IP when traffic leaves the network.
An IPv4 address is 32 bits, giving unique addresses — already exhausted at the global level. IPv6 uses 128 bits, giving , enough to assign roughly addresses to every square meter of Earth's surface. CIDR prefix length defines a subnet of hosts, so a /24 holds addresses (254 usable after network and broadcast).
A GPU server has the private IP 10.0.1.50 inside a VPC, and you need to SSH into it from your home network. Why can't you connect directly to 10.0.1.50?
Packets
Networks move data in packets rather than as one continuous stream. An Ethernet link carries frames whose payload is at most the MTU, usually 1,500 bytes. Inside that, the IP header (20 bytes for IPv4) holds source and destination addresses and a TTL, and the TCP header (20 bytes) holds ports, sequence numbers and a checksum, leaving 1,460 bytes of data. Because packets are forwarded independently, they can take different paths and arrive out of order; TCP reorders them and retransmits losses. A packet larger than a link's MTU must be fragmented or dropped, which is why mismatched MTUs cause mysterious hangs.
An Ethernet MTU of 1500 bytes leaves bytes of TCP payload after 20-byte IP and 20-byte TCP headers, so the protocol overhead is . A 1 GB tensor at 1460 bytes per packet requires packets, each independently routed and reassembled — every lost packet triggers a retransmission of one MSS, not the whole stream.
A 2.4GB PyTorch model checkpoint needs to be downloaded from a server. Approximately how many packets will this require, assuming a standard 1500-byte MTU with 40 bytes of header overhead?
Routing
Routers are specialized network devices that maintain forwarding tables mapping destination IP prefixes to next-hop addresses. When a packet arrives, the router looks up the destination IP in its table and forwards it to the next router closer to the destination. The Border Gateway Protocol (BGP) allows routers across the internet to exchange routing information and find optimal paths.
The traceroute command reveals every router hop between you and a remote server, showing latency at each step. This is invaluable for diagnosing where slowdowns occur — for instance, identifying that the bottleneck in your dataset download is a congested peering point between two ISPs.
Routing inside one network and between networks uses different algorithms. Interior protocols such as OSPF are link-state: every router learns the full topology and runs Dijkstra's shortest-path algorithm, for routers and links. BGP, which connects independent networks, is a path-vector protocol: each network advertises the list of networks a route passes through, and choices follow business policy, not shortest distance. The global IPv4 table holds about a million prefixes, so routers do longest-prefix match in specialized hardware.
Client-Server Model
In the client-server model, one machine (the server) listens for incoming connections on a specific port, and another machine (the client) initiates a connection to request a service. The server processes the request and sends a response. This is the foundation of virtually all ML infrastructure.
ML inference APIs, model registries like Hugging Face Hub, experiment tracking servers like MLflow, and dataset hosting services all follow this pattern. Your training script acts as a client when it pulls data, logs metrics, or calls an API. When you serve a model with FastAPI or TorchServe, your machine becomes the server.
A server with capacity requests/sec and arrival rate has utilization . Queueing theory (M/M/1) says the average response time grows as , which explodes as . This is why production services target : the last 30% of capacity buys you stable tail latency.
You have a FastAPI model server running on a GPU machine and a web application that needs to call it. Describe the client-server interaction for a single inference request.
NAT (Network Address Translation)
NAT translates private IP addresses to public ones at the network boundary. Your entire lab or cloud VPC might share a single public IP address. When a device with private IP 10.0.1.50 sends a packet to the internet, the NAT gateway rewrites the source address to the public IP and tracks the mapping so return traffic reaches the correct internal device.
This is why port forwarding is needed to expose services running on internal machines. Without explicit port forwarding or a public IP, external clients cannot initiate connections to a device behind NAT — they have no way to address it.
A NAT gateway rewrites each outbound flow to its public address and a translated source port, and it must keep those mappings unique. AWS documents that each public IPv4 address on a NAT gateway supports up to 55,000 simultaneous connections to each unique destination, and that you can associate up to 8 addresses, giving 440,000. A training job in which every worker opens many connections to the same storage endpoint can hit that limit and see connection errors, which is why VPC endpoints for S3 bypass the NAT entirely.
Latency vs Throughput
Latency is the time it takes for a single packet to travel from source to destination, measured in milliseconds. Throughput is the total amount of data transferred per second, measured in Mbps or Gbps. These are independent metrics — you can have high throughput with high latency (satellite links) or low latency with low throughput (a slow local connection).
Distributed training cares deeply about both. Gradient synchronization requires low latency (each GPU waits for all others), while data loading requires high throughput (streaming terabytes of training data). Understanding this distinction helps you choose the right network setup for your workload.
Bandwidth-delay product: . Over a 10 Gbps link with 50 ms RTT, — that's the minimum window size needed to keep the pipe full. If your TCP receive window is smaller, you cap throughput at no matter how fat the link is.
A distributed training cluster has 100 Gbps network links with 0.5ms latency. Another has 25 Gbps links with 0.05ms latency. Which is better for (a) data-parallel gradient sync with a 350MB gradient buffer, and (b) pipeline-parallel training with frequent small tensor exchanges?
Theory Exercise
Problem:
A data scientist reports that downloading a 10GB dataset from S3 takes 20 minutes from their office but only 2 minutes from an EC2 instance in the same region. Using your knowledge of IP routing, NAT, and latency vs throughput, explain why this difference exists and suggest improvements.
Hints:
- Consider the network path: office → ISP → internet backbone → AWS region vs EC2 → AWS internal network → S3
- Think about how many NAT translations and router hops each path requires
- Consider the bandwidth available on each path (office internet vs AWS internal)
IP, from the previous topic, delivers packets on a best-effort basis: some get lost, some arrive twice, some arrive out of order. That is useless for a model checkpoint, where one wrong byte corrupts the weights, yet perfect reliability is wasteful for a live video feed, where a frame that arrives late is worthless anyway. Applications need a transport layer that chooses between these trade-offs.
TCP provides a reliable, ordered byte stream. It opens a connection with a three-way handshake, numbers every byte, acknowledges what arrived, retransmits what did not, and adapts its sending rate to both the receiver and the network. The cost is setup latency, head-of-line blocking when a packet is lost, and connection state on both ends. UDP provides bare datagrams with port numbers and a checksum, leaving reliability to the application, which is why QUIC and many real-time systems build on it.
The concepts cover the handshake, reliable delivery, flow and congestion control, UDP datagrams, choosing between the two for ML workloads, and TCP connection states, including the TIME_WAIT exhaustion that takes down busy clients.
Definition
TCP is a connection-oriented transport protocol that gives applications a reliable, ordered byte stream using a three-way handshake, sequence numbers, acknowledgments, retransmission, flow control and congestion control. UDP is a connectionless protocol that sends independent datagrams with ports and a checksum but no delivery, ordering or rate guarantees, leaving those to the application.
In this topic
- 1Three-Way Handshake
- 2Reliable Delivery
- 3Flow Control & Congestion Control
- 4UDP Datagrams
- 5TCP vs UDP for ML
- 6Connection States
Three-Way Handshake
TCP connections begin with a three-step handshake: the client sends a SYN (synchronize) packet, the server responds with SYN-ACK (synchronize-acknowledge), and the client confirms with ACK. This takes one full round-trip before any application data can flow.
For API calls to distant servers, this initial handshake adds measurable latency. With a 100 ms round-trip time (RTT) to the server, that's 100 ms of pure handshake before the client can send its first byte of inference data, and the response arrives about one RTT after that. HTTP/2 and HTTP/3 mitigate this by multiplexing requests over a single connection.
The handshake costs one RTT before the client may send data: SYN goes out ( to RTT), SYN-ACK comes back ( RTT), and the client's ACK can carry the first payload. That payload reaches the server at RTT. On a link with a 100 ms RTT, that's 100 ms of setup before sending and 150 ms before the server sees the first byte, on every new connection — which is precisely why HTTP/2 multiplexes many requests over one connection and why connection pooling is essential at scale.
An ML inference service receives 1000 new HTTP/1.1 connections per second. Each connection requires a TCP handshake with 5ms round-trip latency. If HTTP Keep-Alive is disabled, how much time per second is spent just on handshakes? What if Keep-Alive is enabled and each connection handles 10 requests?
Reliable Delivery
TCP uses sequence numbers and acknowledgments to guarantee that every byte arrives in the correct order. Each byte of data is assigned a sequence number, and the receiver sends ACKs confirming which bytes it has received. If the sender doesn't receive an ACK within a timeout period, it retransmits the lost data.
This is why downloading a 5GB model file always completes correctly — even if individual packets are lost due to network congestion, TCP automatically retransmits them. The tradeoff is that a single lost packet can stall the entire stream (head-of-line blocking) until the retransmission arrives.
TCP detects loss either by triple duplicate ACKs or by RTO expiry, where (RFC 6298). After loss, the congestion window halves (or drops to 1 in classic Reno), so a single dropped packet on a fat link can cut throughput in half for several RTTs while cwnd recovers — the steady-state bandwidth is approximately for loss probability .
During a large model download, you notice the transfer speed periodically drops to near zero for 200-500ms before resuming. What TCP mechanism is likely causing this?
Flow Control & Congestion Control
TCP adjusts its sending rate based on both the receiver's capacity (flow control) and network conditions (congestion control). The receiver advertises a window size indicating how much data it can buffer. The sender also maintains a congestion window that starts small (slow start) and grows exponentially until packet loss is detected, then backs off.
This slow-start behavior means that initial throughput on a new connection is low and ramps up over time. For transferring large datasets, this is a brief warm-up period. But for short-lived connections (individual API calls), you may never reach full bandwidth — another reason to use persistent connections.
Throughput is capped by the window divided by the round-trip time. Without window scaling the window is at most 65,535 bytes, so at a 100 ms RTT a connection tops out near 5.2 Mbps on any link. Linux enables scaling and autotunes the receive buffer up to net.ipv4.tcp_rmem's maximum, 6 MB by default, about 500 Mbps at 100 ms. Filling a 10 Gbps link at 50 ms needs 62.5 MB in flight, so long transfers raise the buffer limits or use parallel streams. Loss also caps throughput, roughly by Mathis et al.
UDP Datagrams
UDP sends independent packets (datagrams) with no connection state, no ordering guarantees, and no retransmission. Each datagram is self-contained with source/destination ports and a simple checksum. The lack of overhead makes UDP faster and lighter than TCP.
UDP is ideal for real-time applications where receiving stale data is worse than losing data entirely. In a real-time video inference pipeline, a dropped frame from 100ms ago is irrelevant — you need the current frame. Retransmitting old frames would only add latency and waste bandwidth.
UDP header is 8 bytes vs TCP's 20+, and there is zero connection state: no SYN, no cwnd, no retransmission queue. The cost is that any packet exceeding the path MTU gets fragmented at the IP layer, and losing one fragment forces the entire datagram to be dropped — so practical UDP payloads are sized to fit within bytes (MTU minus IP and UDP headers).
TCP vs UDP for ML
Choosing between TCP and UDP depends on your ML workload's requirements. Use TCP for model downloads (every byte must arrive), API calls (request-response requires reliability), database queries (data integrity is critical), and SSH (interactive sessions need ordering). Use UDP for real-time video inference (latest frame matters most), sensor data streaming (occasional loss is acceptable), and some custom distributed training communication layers where the application handles reliability.
In practice, most ML infrastructure uses TCP because reliability is almost always required. UDP appears mainly in specialized real-time inference pipelines and some high-performance computing frameworks that implement their own reliability layer on top of UDP for finer control.
Distributed gradient sync over TCP suffers from head-of-line blocking: one lost packet stalls every later packet on the same stream. NCCL and RDMA bypass this by using lossless fabrics (RoCE, InfiniBand) where the link layer guarantees delivery — eliminating the recovery latency that would otherwise dominate -worker all-reduce step times.
Connection States
A TCP connection moves through states: LISTEN on the server, SYN_SENT and SYN_RECEIVED during the handshake, ESTABLISHED while data flows, then FIN_WAIT, CLOSE_WAIT and LAST_ACK as each side closes. The side that closes first enters TIME_WAIT, which Linux holds for a fixed 60 seconds so delayed packets from the old connection cannot corrupt a new one with the same addresses and ports. A pile of CLOSE_WAIT means your application forgot to close sockets. A pile of TIME_WAIT on a client that opens a fresh connection per request can exhaust its ephemeral ports. Inspect states with ss -tan.
A connection is identified by four numbers: local address, local port, remote address and remote port. Toward a single server address and port, a client can vary only its local port, so the sustained rate of new connections is at most the size of the ephemeral range divided by the TIME_WAIT duration: 28,232 / 60, about 470 per second, with Linux defaults. Keep-alive changes the arithmetic completely: 600 requests per second over 50 pooled connections creates no new connections at all in steady state.
An API gateway forwards every request to a model server at 10.0.2.15:8000 over a brand-new TCP connection and closes it afterwards. At about 600 requests per second the gateway logs 'Cannot assign requested address', and ss -tan state time-wait on the gateway counts about 28,000 sockets. What is happening, and how do you fix it?
Theory Exercise
Problem:
You are designing a real-time object detection system for autonomous driving. The camera feeds (30 fps, 1920x1080) are sent from the vehicle to an edge server for inference, and detection results (bounding boxes) are sent back. Should you use TCP or UDP for (a) the video feed, (b) the detection results, and (c) the model update downloads? Justify each choice.
Hints:
- Consider what happens if a video frame is delayed by 200ms waiting for a retransmission
- Think about whether missing a detection result for one frame is acceptable vs missing a model update byte
- Consider the data sizes: video frames are large, detection results are small, model files are very large
TCP, from the previous topic, delivers a reliable stream of bytes, but it says nothing about what those bytes mean. A client asking a model server for a prediction needs to say which model, send the input, prove who it is and learn whether the call succeeded, and a server needs to answer thousands of such clients without custom code for each. That shared vocabulary is HTTP.
An HTTP exchange is a request with a method, a path, headers and an optional body, followed by a response with a status code, headers and a body. Its rules are simple enough that curl, browsers, load balancers and every model-serving framework interoperate. HTTPS wraps the same exchange in TLS, which authenticates the server with a certificate and encrypts everything in transit, so prompts, images and API keys cannot be read or altered on the way.
The concepts cover methods and idempotency, status codes and how clients should react to them, headers, TLS, request bodies and their serialization cost, and connection reuse with keep-alive, HTTP/2 multiplexing and HTTP/3 over QUIC.
Definition
HTTP is a stateless request-response application protocol: a client sends a method, target path, headers and optional body, and the server returns a status code, headers and body. HTTPS is HTTP carried over TLS, which authenticates the server with a certificate, negotiates session keys and encrypts and integrity-protects all data in transit.
In this topic
- 1HTTP Methods
- 2Status Codes
- 3Headers
- 4TLS/SSL Encryption
- 5Request Body
- 6Keep-Alive & HTTP/2
HTTP Methods
HTTP defines a set of request methods that indicate the desired action. GET retrieves data without side effects. POST sends data to create a resource or trigger processing (inference requests). PUT replaces a resource entirely. PATCH applies partial updates. DELETE removes a resource. HEAD retrieves headers only — useful for checking if a model file exists without downloading it.
For ML APIs, the most common pattern is POST for inference (sending input data and receiving predictions) and GET for metadata (listing available models, checking health status). Well-designed APIs use methods consistently: POST /predict for inference, GET /models for listing, DELETE /models/{id} for cleanup.
Idempotency is a mathematical property: . GET, PUT, DELETE, HEAD are idempotent — replaying them yields identical state. POST is not, so a retry on a flaky network can create duplicate resources. This is why production clients retry GET freely but require an idempotency key for POST: it's the difference between safe automatic retries and double-charging a customer.
Status Codes
HTTP status codes are three-digit numbers that communicate the result of a request. 2xx codes indicate success (200 OK, 201 Created). 4xx codes indicate client errors (400 Bad Request for malformed input, 401 Unauthorized for missing API keys, 403 Forbidden, 404 Not Found, 429 Too Many Requests for rate limiting). 5xx codes indicate server errors (500 Internal Server Error, 503 Service Unavailable when the model is still loading).
For ML APIs, 429 (rate limited) and 503 (model loading) are especially important to handle. Implement exponential backoff for 429 responses and readiness probes for 503 during model warm-up.
Status classes are what availability is measured in. A typical availability SLI counts responses that are not 5xx divided by all valid requests, and 4xx responses are usually excluded because they reflect client mistakes. A 99.9 percent target allows 0.1 percent errors, about 43 minutes of full outage in a 30-day month, or 1,000 failed requests out of every million. Retrying 503 and 429 with exponential backoff and jitter keeps a brief overload from turning into a retry storm that pushes the error rate higher.
Your inference client receives a 503 status code with the body '{"error": "model loading"}' when calling a freshly deployed model server. How should the client handle this?
Headers
Headers are name-value pairs that carry metadata about a request or response. Authorization carries credentials such as a bearer token. Content-Type declares the body format, for example application/json or multipart/form-data, and Accept states what format the client wants back. Content-Length, Cache-Control and Retry-After shape transfer, caching and retries, and a request-ID header lets you follow one call through several services. RFC 6648 deprecated the old convention of prefixing custom headers with X-, so newer standards drop it, as in RateLimit-Limit, though many APIs still send X-RateLimit-Remaining. Header names are case-insensitive, and HTTP/2 sends them lowercase.
Headers are repeated on every request, so their size multiplies. An HTTP/1.1 request with cookies, an authorization token and user agent can easily carry several hundred bytes of headers before the body. At 10,000 small inference calls per second, 800 bytes of headers is 8 MB/s of overhead. HTTP/2's HPACK compression keeps a table of headers already sent on the connection and replaces repeats with short indexes, so a token sent once costs a byte or two on later requests.
TLS/SSL Encryption
HTTPS runs TLS between TCP and HTTP. In the handshake the server presents a certificate chain that the client checks against trusted authorities and the requested hostname, and both sides run an ephemeral Diffie-Hellman exchange, usually ECDHE, to derive session keys. Bulk data is then encrypted with a fast authenticated cipher such as AES-GCM or ChaCha20-Poly1305. TLS 1.3 removed static RSA key exchange and needs one round trip for the handshake, versus two for TLS 1.2. SSL is the obsolete predecessor, kept only in names. Let's Encrypt issues free certificates with automated renewal, so production model APIs have no excuse to skip HTTPS.
A full TLS 1.2 handshake costs 2 RTTs (ClientHello → ServerHello+Cert → ClientKeyExchange+Finished → ServerFinished) on top of TCP's 1 RTT, so 3 RTTs total before the first byte of payload. TLS 1.3 cuts handshakes to 1 RTT (or 0-RTT with session resumption), saving up to 200 ms on intercontinental links — the single biggest latency win of the decade for HTTPS.
Request Body
POST, PUT and PATCH requests carry a body, and the Content-Type header tells the server how to parse it. ML APIs use JSON for structured inputs such as text and parameters, multipart/form-data for file uploads, and sometimes raw binary. JSON is costly for numeric arrays: on the test machine, a 224 by 224 by 3 float32 tensor is 602,112 bytes raw but 3.15 MB as a JSON list, 5.2 times larger, and parsing it took about 34 ms. The same image as uint8 pixels is 150,528 bytes raw, and as a JPEG far less. Send compressed images or binary tensors, not floats as text.
Text encoding inflates numbers because each float32 value, 4 bytes in memory, prints as up to about 20 characters plus a comma: the probe averaged 21 characters per value for random floats in Python's json module, a 5.2x blow-up. Base64 of the raw bytes costs only 4/3, about 803 KB for the same tensor. Parsing cost scales with text size; json.loads ran at roughly 11 ms per MB. That is why high-throughput servers such as Triton accept binary tensor data or protobuf alongside their JSON APIs.
Keep-Alive & HTTP/2
HTTP/1.1 reuses TCP connections by default (Keep-Alive), avoiding the overhead of repeated handshakes. However, HTTP/1.1 processes requests sequentially on each connection — a slow response blocks subsequent requests on that connection.
HTTP/2 solves this with multiplexing: multiple requests and responses flow over a single TCP connection simultaneously. This dramatically reduces latency for batch inference calls where you send many predictions at once. HTTP/3 goes further by using QUIC (UDP-based) to eliminate head-of-line blocking at the transport layer.
A new HTTPS connection costs one RTT for the TCP handshake plus one for a TLS 1.3 handshake, or two for TLS 1.2, before the request can leave. Keep-alive pays that once and reuses the connection, so over requests the setup cost per request falls to about RTT. HTTP/2 also lets concurrent requests share one connection and congestion window instead of opening connections. HTTP/3 runs over QUIC, which combines transport and TLS setup into one round trip and removes TCP's head-of-line blocking.
Related Problems on PixelBank
HTTP requests, from the previous topic, are addressed to names like api.example.com, but packets need IP addresses, and the addresses behind a service change whenever a load balancer scales or a server is replaced. Something must translate names to current addresses, for billions of clients, in a few milliseconds, without one central server that every lookup depends on. When that system is slow or wrong, the model server is healthy and still unreachable.
The Domain Name System solves this with a distributed, cached hierarchy. Your machine asks a recursive resolver, which, on a cache miss, walks from the root servers to the top-level-domain servers to the domain's authoritative nameservers and caches the answer for the record's TTL. Records come in types: A and AAAA hold addresses, CNAME holds an alias, and others hold mail servers and text such as verification tokens.
The concepts cover A records, CNAMEs and the apex restriction, TTLs, multi-layer caching and why changes are never instant, round-robin DNS as crude load balancing, and private DNS zones that name internal services inside a cloud network.
Definition
The Domain Name System is a hierarchical, distributed database that maps domain names to resource records such as IPv4 (A) and IPv6 (AAAA) addresses and aliases (CNAME). Recursive resolvers answer client queries by following delegations from the root to authoritative nameservers, and every answer is cached for its record's time to live.
In this topic
- 1A Record
- 2CNAME Record
- 3TTL (Time To Live)
- 4DNS Caching
- 5Round-Robin DNS
- 6Private DNS
A Record
An A record maps a domain name directly to an IPv4 address. For example, api.mymodel.com → 203.0.113.50. This is the most common DNS record type you'll configure when deploying ML services. A single domain can have multiple A records pointing to different IP addresses, providing basic round-robin load balancing.
When you deploy a model API on a cloud server, you create an A record pointing your custom domain to the server's public IP. If the server's IP changes (e.g., after restarting a cloud instance without an Elastic IP), you need to update the A record.
An A record returns a 4-byte IPv4 address; AAAA returns a 16-byte IPv6 address. DNS responses are typically <512 bytes to fit a single UDP packet (the original RFC 1035 limit) — anything larger forces TCP fallback or EDNS0, adding RTT. For ML inference endpoints behind a CDN, the A record itself is the load balancer's hop-zero entry point.
CNAME Record
A CNAME (Canonical Name) record creates an alias that points to another domain name rather than directly to an IP address. For example, inference.myapp.com → my-gpu-server.us-east-1.compute.amazonaws.com. This is useful for pointing your custom domain to a cloud load balancer or CDN endpoint.
CNAME records add an extra DNS lookup (resolving the target domain), but they decouple your domain from specific IP addresses. If the cloud provider changes the load balancer's IP, your CNAME still works because it points to a name, not an address.
A CNAME adds indirection: resolving api.example.com may follow api.example.com, then a load balancer's name, then an address, and on a cold cache each step can cost another query. The apex restriction has a different cause. RFC 1034 says that if a CNAME exists at a name, no other data may exist there, but the zone apex must hold SOA and NS records, so it cannot also hold a CNAME. Providers work around this with ALIAS or ANAME records that they resolve themselves and answer with plain A records.
You want to point api.mymodel.com to an AWS Application Load Balancer. Should you use an A record or a CNAME? What about for the apex domain mymodel.com?
TTL (Time To Live)
TTL specifies how long (in seconds) a DNS record should be cached by resolvers and clients before re-querying. A low TTL (60 seconds) allows fast failover — if you change a server IP, traffic redirects within a minute. A high TTL (86400 seconds / 24 hours) reduces DNS query load but makes changes slow to propagate.
For ML services: use a low TTL (60-300s) during deployments or migrations when you might need to quickly switch servers. Use a higher TTL (3600-86400s) for stable production services to reduce DNS lookup latency.
Each caching resolver re-asks the authoritative server at most once per TTL for a given name, so authoritative load scales with the number of distinct resolvers divided by the TTL, not with the number of end users. Cutting the TTL from 3,600 to 60 seconds can raise that load up to 60 times. The worst-case staleness after a change is roughly the old TTL, which is why a migration lowers the TTL one full old-TTL period before switching the record.
DNS Caching
DNS results are cached at multiple levels: the browser cache, the operating system's resolver cache, your local DNS resolver, your ISP's recursive resolver, and potentially other intermediate resolvers. Each level respects the TTL value from the authoritative response.
This multi-level caching means DNS changes are never instant — old records persist at each cache layer until their TTL expires. When migrating an ML API to a new server, reduce the TTL to 60 seconds several hours before the migration, perform the switch, and then raise the TTL back up once you've confirmed stability.
Nested cache hierarchy (browser → OS → resolver → root) follows the standard cache hit-rate model: if each layer has hit rate , total miss probability is . With browser hit rate and OS hit rate , only of lookups ever leave the machine — which is also why a stale DNS entry can persist for hours after you change a record.
Round-Robin DNS
When multiple A records exist for the same domain, DNS resolvers typically rotate through them, distributing requests across servers. This provides a basic form of load balancing for model inference endpoints without requiring a dedicated load balancer.
However, round-robin DNS has limitations: it doesn't check server health (traffic goes to crashed servers), doesn't account for server load (busy and idle servers get equal traffic), and client-side caching can cause uneven distribution. For production ML services, use a proper load balancer (Nginx, HAProxy, cloud LB) instead.
If a DNS server returns A records and clients pick uniformly at random, expected load per backend is with variance . With client-side caching, the effective fan-out is much smaller than — round-robin DNS is a probabilistic load balancer, not a real one. For tight SLOs you need an L4 load balancer that can actually observe request rates.
Private DNS
Within cloud VPCs, internal DNS resolves private hostnames (gpu-worker-1.internal) to private IP addresses without exposing them to the internet. AWS Route53 Private Hosted Zones, GCP Cloud DNS private zones, and similar services let you create internal DNS records accessible only within your network.
This is essential for ML infrastructure where multiple services communicate internally: the training coordinator, parameter server, data loading service, and monitoring stack can all have human-readable internal DNS names instead of hard-coded IP addresses.
AWS Route 53 private hosted zones resolve names only inside a VPC, so db.internal returns the RFC 1918 private IP. Without it, you'd need to plumb IPs through environment variables — and rotating an IP would require a deploy. Private DNS reduces the change set on a database failover from to .
DNS, from the previous topic, gets a client to the right machine, but a GPU server runs many services at once: SSH, Jupyter, TensorBoard, a model API, a metrics exporter. Each needs its own address on that machine, and most of them should never be reachable from the internet. A Jupyter server exposed without a token on a public address is a remote shell for anyone who scans for it, and scanners sweep the whole IPv4 space in under an hour.
Ports solve the first problem. A port is a 16-bit number, so each address has 65,536 of them, and a connection is identified by the addresses and ports at both ends. Firewalls solve the second: on the host, iptables or nftables rules decide which packets are accepted, and in the cloud, security groups filter traffic before it reaches the instance.
The concepts cover well-known and registered ports, the ports common ML tools use, iptables rule ordering, cloud security groups, SSH port forwarding as a safer alternative to opening ports, and the difference between binding to 127.0.0.1 and 0.0.0.0.
Definition
A port is a 16-bit number that identifies an application endpoint on a host, so one IP address can serve many services. A firewall is a set of ordered or stateful rules, on the host (iptables, nftables) or in the network (cloud security groups), that accepts or drops traffic based on addresses, protocol, ports and connection state.
In this topic
- 1Well-Known Ports
- 2ML-Specific Ports
- 3iptables
- 4Security Groups
- 5Port Forwarding & SSH Tunneling
- 6Binding to 0.0.0.0 vs 127.0.0.1
Well-Known Ports
IANA divides the 65,536 port numbers into three ranges. Well-known ports, 0 to 1023, belong to standard protocols: 22 for SSH, 53 for DNS, 80 for HTTP and 443 for HTTPS. Registered ports, 1024 to 49151, are assigned to applications, such as 3306 for MySQL, 5432 for PostgreSQL and 6379 for Redis. Dynamic ports above that are for temporary client use, though Linux actually picks client ports from 32768 to 60999. On Linux, binding below 1024 traditionally requires root or the CAP_NET_BIND_SERVICE capability. Standard numbers let firewalls and engineers recognize traffic at a glance, but anything can listen on any port.
A port field is 16 bits, so there are values per address and protocol. The privileged boundary is a kernel setting, net.ipv4.ip_unprivileged_port_start, which defaults to 1024. Client ports come from net.ipv4.ip_local_port_range, 32768 to 60999 by default, which is 28,232 ports rather than IANA's 16,384-port dynamic range. A server needs only one port for any number of clients, since each connection is distinguished by the client's address and port.
ML-Specific Ports
The ML ecosystem uses several well-known registered ports: 8888 (Jupyter Notebook/Lab), 6006 (TensorBoard), 8000/8080 (FastAPI, Flask, and generic web servers), 8501 (TensorFlow Serving REST API), 8500 (TensorFlow Serving gRPC), 7860 (Gradio), 5000 (MLflow tracking server), 9090 (Prometheus metrics), and 3000 (Grafana dashboards).
When setting up a GPU server, you'll need to open these ports in the firewall for the specific tools you're using. A typical research setup might need SSH (22), Jupyter (8888), TensorBoard (6006), and a model server (8000).
Distributed training adds ports that firewalls often block. torchrun's rendezvous listens on the master port, 29500 by default, and NCCL then opens its own connections between every pair of workers on ephemeral ports. If a firewall or security group allows only 22 and 29500, rendezvous may succeed and the first collective then hangs until the process-group timeout. The usual fix in a cluster is to allow all traffic between members of the same security group rather than listing ports one by one.
iptables
iptables is the standard Linux firewall that filters network packets based on configurable rules. It organizes rules into chains: INPUT (incoming traffic), OUTPUT (outgoing traffic), and FORWARD (traffic being routed through). Each rule specifies conditions (source IP, destination port, protocol) and an action (ACCEPT, DROP, REJECT).
Rules are evaluated in order — the first matching rule wins. A common pattern is to ACCEPT specific ports (SSH, HTTPS, model server), then DROP everything else as a catch-all. Always place ACCEPT rules before DROP rules, or your allowed traffic will be blocked.
iptables checks a packet against the rules of a chain in order until one matches, so the work per packet grows linearly with the number of rules ahead of the match. Connection tracking makes this cheap in practice: a rule accepting ESTABLISHED and RELATED traffic near the top means only the first packet of each connection walks the full list. Long lists of addresses belong in an ipset or an nftables set, which is matched with one hash lookup instead of thousands of separate rules.
You have an iptables rule iptables -A INPUT -p tcp -j DROP followed by iptables -A INPUT -p tcp --dport 22 -j ACCEPT. Why can't you SSH into the server?
Security Groups
Cloud-level firewalls (AWS Security Groups, GCP Firewall Rules, Azure NSGs) control traffic to cloud instances. They operate at the cloud infrastructure level, separate from the instance's own iptables. Security groups are stateful — if you allow inbound traffic on a port, the corresponding outbound response is automatically allowed.
Always prefer security groups over iptables in cloud environments. They're easier to manage, can be shared across instances, and provide a clear audit trail. Reserve iptables for edge cases where you need finer-grained control.
AWS quotas bound how much a security group can express: by default 60 inbound and 60 outbound rules per group, and 5 groups per network interface, adjustable up to 16, with the product of the two capped at 1,000 rules. Because groups are stateful, a reply to an allowed request is always let back in, so you write one rule per flow instead of two. Network ACLs are a separate stateless layer at the subnet, evaluated in numbered order, and need explicit return-traffic rules.
Port Forwarding & SSH Tunneling
Port forwarding redirects traffic from one port to another. SSH tunneling (the -L flag) is the most common form for ML engineers: ssh -L 8888:localhost:8888 gpu-server forwards your local port 8888 to the remote server's port 8888. You can then access Jupyter on the GPU server by opening localhost:8888 in your local browser.
This is more secure than exposing ports through firewalls because the traffic is encrypted through the SSH tunnel. You can forward multiple ports simultaneously: -L 8888:localhost:8888 -L 6006:localhost:6006 for Jupyter and TensorBoard.
A tunnel's throughput is usually limited by flow control rather than encryption. Modern CPUs encrypt with AES-GCM at several gigabytes per second per core (openssl speed measured about 6 GB/s on the test machine), but each SSH channel has its own window, and on a long, high-latency path a fixed window divided by the RTT caps the rate just as a small TCP window does. For dashboards and notebooks that hardly matters. For bulk data, use rsync or a direct transfer instead of pushing gigabytes through a forwarded port.
Binding to 0.0.0.0 vs 127.0.0.1
Services bound to 127.0.0.1 (localhost) only accept connections from the same machine. Services bound to 0.0.0.0 accept connections from any network interface — meaning they're accessible from outside if the firewall allows it.
For development, bind to 127.0.0.1 for safety. For production ML servers that need to accept external requests, bind to 0.0.0.0 but protect with a firewall. A common mistake is running a model server bound to 127.0.0.1 and wondering why external clients can't connect, or binding to 0.0.0.0 without a firewall and exposing the service to the entire internet.
Most data-science tools already default to loopback: Jupyter Server's ip setting defaults to localhost, and TensorBoard listens on localhost unless you pass --bind_all. Exposure usually comes from overriding that, for example jupyter lab --ip=0.0.0.0 inside a container, or from Docker: Docker's documentation notes that publishing a port with -p inserts NAT rules that bypass ufw, so a host firewall may not protect it. Publishing as -p 127.0.0.1:8888:8888 keeps the port local, and an SSH tunnel then reaches it.
Related Problems on PixelBank
Your training runs on a GPU server in a data center. You need a shell on it, a way to copy checkpoints off it, and a way to see the Jupyter or TensorBoard page it serves, and all of it must cross the internet without leaking passwords or exposing those services to everyone. The ports and firewalls from the previous topic say which doors are open; SSH is the one door you open on purpose.
SSH gives you an encrypted, authenticated channel to a remote machine. The server proves its identity with a host key that your client remembers, and you prove yours with a key pair instead of a password. The same connection can carry a shell, file transfers and tunnels that forward ports in either direction, so one open port, 22, gives access to everything else on the machine.
The concepts cover key pairs and why Ed25519 is the default choice, the ssh_config file and its first-value-wins rule, local port forwarding for notebooks and dashboards, scp versus rsync for moving checkpoints, sshfs mounts, and ssh-agent with the risks of agent forwarding compared with ProxyJump.
Definition
SSH (Secure Shell) is a protocol for encrypted, authenticated remote login and command execution over an untrusted network. The server authenticates with a host key and the user typically with a public-private key pair. One SSH connection can multiplex interactive shells, file transfer (scp, sftp, rsync over SSH) and port forwarding tunnels.
In this topic
- 1SSH Key Pair
- 2SSH Config File
- 3Local Port Forwarding (-L)
- 4SCP vs rsync
- 5sshfs
- 6SSH Agent
SSH Key Pair
SSH authentication uses asymmetric cryptography. The private key stays on your machine (~/.ssh/id_ed25519) and must never be shared. The public key goes on remote servers (~/.ssh/authorized_keys). ed25519 keys are recommended over RSA — they provide equivalent security with shorter key lengths and faster operations.
The private key can be protected with a passphrase that must be entered each time the key is used. ssh-agent caches the decrypted key in memory so you don't re-enter the passphrase for every connection within a session.
Key size and security level are different numbers. A 3,072-bit RSA key is rated at about 128 bits of security, because factoring algorithms are far better than brute force, while a 256-bit Ed25519 key reaches about 128 bits against the best known attacks on its curve. So Ed25519 gives similar strength with a public key of 68 characters instead of several hundred, and signs and verifies faster. Generate one with ssh-keygen -t ed25519 and protect it with a passphrase.
SSH Config File
The ~/.ssh/config file defines host aliases with all connection parameters, eliminating repetitive command-line flags. You can specify the hostname, username, identity file, port number, port forwarding rules, and connection keep-alive settings for each server.
This file makes SSH scripting much cleaner and reduces errors. Instead of remembering complex connection strings, you define them once and use a short alias. The config also supports wildcard patterns (Host *.internal) for applying settings to groups of hosts.
ssh_config is not first-match-wins by block. ssh reads the file top to bottom, applies every Host or Match block that matches, and for each option keeps the first value it finds. That is why the manual advises putting host-specific declarations near the beginning and general defaults at the end. Settings given on the command line take precedence over the file, and the user's file takes precedence over the system-wide one. ssh -G host prints the final merged configuration, which settles any doubt.
You have three GPU servers (gpu-1, gpu-2, gpu-3) that all use the same SSH key, the same username (ubuntu), and all need Jupyter port forwarding. Write a single SSH config entry that covers all three.
Local Port Forwarding (-L)
SSH local port forwarding tunnels a remote port to your local machine, creating an encrypted channel for accessing remote web services. The command ssh -L 8888:localhost:8888 gpu-server forwards your local port 8888 to port 8888 on the GPU server.
This is the standard way to access Jupyter notebooks, TensorBoard, MLflow, and Gradio interfaces running on remote servers. The traffic is encrypted through the SSH tunnel, so you don't need to expose these ports through firewalls.
In -L local:host:remote, the local port opens on your laptop, and host and remote are resolved from the SSH server, so localhost there means the server itself. Each browser connection to the local port becomes one channel inside the single SSH connection, adding the laptop-to-server round trip to every request. Because the remote service only needs to listen on its own loopback address, the cloud firewall can keep every port except 22 closed. Add -N to forward without opening a shell.
SCP vs rsync
scp copies files over SSH; since OpenSSH 9.0 it uses the SFTP protocol underneath, but it still copies whole files and cannot resume. rsync compares source and destination first: by default it skips files whose size and modification time match, and for changed files it uses a rolling checksum to send only the differing blocks. With --partial, or -P, which also shows progress, an interrupted transfer keeps the partial file and the next run reuses it. The usual command is rsync -avhP gpu:~/checkpoints/ ./local/, where -a preserves permissions and times. The trailing slash matters: it copies the directory's contents, not the directory.
SCP transfers bytes in time with no resume support — a failed 100 GB transfer means starting over. rsync uses a rolling checksum to send only changed chunks: for a file of size with changed blocks of size , it transfers bytes (the is checksums). For largely-unchanged datasets, this is the difference between minutes and hours.
sshfs
sshfs mounts a remote filesystem locally via SSH using the FUSE (Filesystem in Userspace) framework. After mounting, you can browse remote files with your local file manager, open them in your local IDE, and run local tools on remote data as if it were on your machine.
This is particularly useful for editing training scripts and configuration files on a remote server using VS Code or another local editor. The command sshfs gpu:~/project /mnt/remote-project makes the remote project directory appear as a local directory.
sshfs mounts a remote directory as a local filesystem via FUSE, but every read() syscall becomes a network round-trip. Random 4 KB reads on a 100 ms RTT link cap at IOPS, or 40 KB/s — three orders of magnitude slower than a local SSD. It's a convenience tool for small files, not a substitute for actually copying data.
SSH Agent
ssh-agent is a daemon that caches decrypted private keys in memory, eliminating the need to re-enter your passphrase for every SSH connection. ssh-add loads keys into the agent. Agent forwarding (-A) passes your local keys to remote servers, letting you chain SSH connections without copying private keys to intermediate machines.
Use agent forwarding carefully — any user with root access on the intermediate server can use your forwarded keys while you're connected. For high-security environments, use ProxyJump instead of agent forwarding.
Agent forwarding never sends the private key, but it lets the remote host ask your agent to sign. The ssh manual warns that anyone able to bypass file permissions on that host, such as root, can use the forwarded agent socket while you are connected. Each intermediate machine you forward through is therefore a machine that can authenticate as you. ProxyJump avoids this: ssh -J bastion target runs the second connection's authentication from your laptop through the bastion, so the bastion only relays encrypted bytes.
HTTP, from earlier in the chapter, gives a request-response format, but it does not say how to lay out an API: which URLs exist, what the request looks like, how errors and versions are expressed. Two teams building a feature store and a model server will each invent something different unless they follow a convention. And when one service calls another thousands of times per second with tensors as payloads, JSON text and per-request overhead become the bottleneck.
REST and gRPC are the two dominant answers. REST models the API as resources addressed by URLs and manipulated with HTTP methods, usually exchanging JSON, so any language and curl can talk to it. gRPC defines services and messages in a Protocol Buffers schema, generates client and server code, and sends compact binary messages over HTTP/2 with streaming in both directions. Most ML platforms expose REST to external users and use gRPC between internal services.
The concepts cover resource design and versioning, JSON serialization costs, Protocol Buffers, gRPC's four call types, API authentication, and rate limiting with token buckets.
Definition
REST is an architectural style in which an API exposes resources at URLs, manipulated with standard HTTP methods and usually represented as JSON, with stateless requests. gRPC is a remote procedure call framework in which services and messages are defined in Protocol Buffers schemas and exchanged as binary messages over HTTP/2, supporting unary and streaming calls.
In this topic
- 1REST Resource Design
- 2JSON Serialization
- 3Protocol Buffers
- 4gRPC Streaming
- 5API Authentication
- 6Rate Limiting
REST Resource Design
REST APIs organize endpoints around resources (nouns), not actions (verbs). A well-designed ML API uses paths like /v1/models/resnet50/predict rather than /v1/runPrediction. Version your API with a path prefix (/v1/, /v2/) to allow breaking changes without disrupting existing clients.
Return consistent JSON structures with clear status and data fields. Error responses should include a machine-readable error code, a human-readable message, and optionally a details field for debugging. This consistency makes your API predictable and easy to integrate with.
REST encodes operations as pairs over HTTP, making the API surface roughly for verbs and resources. With 4 verbs and 10 resources, that's 40 endpoints — but most are CRUD boilerplate. The discipline of resource modeling forces you to think in nouns first, which keeps the API stable as features evolve.
Design the REST API endpoints for an ML model serving platform that supports multiple models, versioning, batch predictions, and health monitoring.
JSON Serialization
JSON is the default format for REST APIs because it is human-readable, self-describing and supported by every language, which makes debugging with curl easy. Its weakness is numeric data. On the test machine, a 224 by 224 by 3 float32 tensor took 602,112 bytes in memory but 3.15 MB as a JSON array, about 5.2 times larger, because each float printed as about 20 characters. Parsing it took about 34 ms, against under a microsecond to reinterpret the raw bytes. For images, send the compressed file, base64-encoded or as multipart; for tensors, use a binary format such as protobuf or raw bytes.
JSON parsing is linear in the size of the text, but with a large constant: every character is examined, numbers are converted from decimal and lists of Python objects are built. The probe measured about 11 ms per megabyte for json.loads, so a 3 MB tensor payload costs tens of milliseconds of CPU per request, comparable to the model's own forward pass. Binary formats avoid the conversion entirely: a packed float array in protobuf is the raw 4 bytes per value plus a few bytes of framing.
Protocol Buffers
Protocol Buffers is Google's schema-based binary serialization format. You declare messages in a .proto file with typed, numbered fields, and the compiler generates code for Python, Go, C++ and other languages. On the wire, each field is a field number and type tag followed by the value: integers use variable-length encoding, so small numbers take one byte, and repeated floats are packed at 4 bytes each. Messages are typically several times smaller than JSON for numeric data and much faster to parse. Field numbers are the contract: you may add fields, but never reuse or renumber old ones, or old clients misread new data.
Varint encoding stores 7 bits per byte, so values below 128 take 1 byte and values below 16,384 take 2, instead of a fixed 4 or 8. Floats gain nothing from varints: a packed repeated float field for the 150,528-value image tensor is 602,112 bytes of data plus a 4-byte header, the same as raw memory and 5.2 times smaller than JSON. The win for tensors is avoiding text conversion; the win for typical records is that field names never travel, only their numbers.
gRPC Streaming
gRPC supports four communication patterns: unary (single request, single response — like REST), server streaming (one request, many responses — ideal for progressive inference results), client streaming (many requests, one response — for sending data in chunks), and bidirectional streaming (both sides send messages concurrently).
Server streaming is particularly useful for ML: the client sends an image, and the server streams back detection results as they're found, or streams intermediate attention maps during generation. Bidirectional streaming enables real-time video inference where frames flow in and detections flow out simultaneously.
Each gRPC message on a stream is prefixed by 5 bytes, a 1-byte compression flag and a 4-byte length, and carried in HTTP/2 DATA frames with 9-byte frame headers, while the call's headers are sent once when the stream opens. A server-streaming call that returns 500 generated tokens therefore pays the request overhead once and a few bytes per token. A REST client polling for tokens, or making one request each, would repeat headers, authentication and routing work every time.
API Authentication
API keys are the simplest authentication method — a secret string passed in the Authorization header. They're easy to implement but hard to scope (an API key typically grants full access) and revoke (changing the key affects all users).
JWT (JSON Web Tokens) are stateless tokens that contain encoded user information and permissions. OAuth2 provides scoped permissions with token expiration and refresh. For service-to-service communication within a cluster, mutual TLS (mTLS) ensures both sides verify each other's identity using certificates.
Bearer tokens (JWTs) are typically – bytes and validated stateless via signature check ( per request, no DB lookup). Compared to session cookies that hit a session store, JWTs trade server-side state for client-side bandwidth: at 10K RPS, that's – MB/s of token data, easily justified by eliminating the session DB.
Rate Limiting
Rate limiting restricts the number of API requests a client can make within a time window to prevent abuse and ensure fair resource allocation. Standard headers communicate limits: X-RateLimit-Limit (max requests per window), X-RateLimit-Remaining (requests left), and X-RateLimit-Reset (when the window resets).
When a client exceeds the limit, the server returns 429 Too Many Requests. The client should implement exponential backoff: wait 1s, then 2s, then 4s, up to a maximum delay. For ML APIs, rate limiting is essential because each inference request consumes expensive GPU compute.
Token bucket: refill rate tokens/sec, capacity . The maximum burst is , sustained rate is . With , a client can fire 100 requests instantly, then must wait ms between further requests. This is the canonical algorithm because it's per request and naturally accommodates bursty workloads — exactly what API clients look like.
Related Problems on PixelBank
Add a second GPU server to a training job and you might expect it to run twice as fast. Instead, step time can stay flat or even rise, because after every backward pass all workers must exchange gradients, and the network, not the GPUs, sets the pace. The same happens on the input side: a GPU that can process thousands of images per second sits idle if the data arrives from object storage at a fraction of that rate.
This topic applies everything earlier in the chapter, from TCP windows to firewalls, to the traffic patterns of ML. Data-parallel training synchronizes gradients with all-reduce, which libraries such as NCCL implement with ring and tree algorithms that use every link. Bandwidth falls by orders of magnitude from GPU memory to NVLink to InfiniBand or Ethernet to object storage, and that hierarchy decides where each kind of parallelism can run.
The concepts cover all-reduce algorithms and their cost, NCCL, the bandwidth hierarchy, data-loading bottlenecks, multipart transfers to and from object storage, and network topology and placement.
Definition
Networking for ML is the design of communication for training and serving at scale: collective operations such as all-reduce between GPUs (implemented by libraries such as NCCL over NVLink, InfiniBand or Ethernet), high-throughput data loading from storage, and instance placement, all chosen so that communication overlaps with or stays small relative to computation.
In this topic
- 1All-Reduce
- 2NCCL
- 3Bandwidth Hierarchy
- 4Data Pipeline Bottleneck
- 5Multipart Transfers
- 6Network Topology
All-Reduce
All-reduce is the fundamental communication pattern in data-parallel training. Every GPU sends its gradient tensor to every other GPU and receives the aggregated (summed) result. The naive approach of sending all data to one node and broadcasting back is bandwidth-wasteful.
Ring all-reduce is bandwidth-optimal: each of N GPUs sends and receives only 2*(N-1)/N of the total data. The GPUs form a logical ring, and data is passed in two phases — scatter-reduce and all-gather — each taking N-1 steps. This makes the communication cost independent of the number of GPUs (in terms of bandwidth), scaling linearly with the gradient size alone.
Ring all-reduce on workers transmits each gradient byte exactly times, asymptotically . Step time is where is gradient size and is per-link bandwidth. The crucial property: it's bandwidth-optimal and independent of for large — adding workers does not slow down the per-step communication.
You have 8 GPUs, each with a 1GB gradient tensor. Compare the total data transferred with (a) naive all-reduce (send to one node, broadcast back) and (b) ring all-reduce.
NCCL
NCCL (NVIDIA Collective Communication Library, pronounced 'nickel') is NVIDIA's optimized library for GPU-to-GPU communication. It automatically detects the network topology (NVLink connections, PCIe layout, InfiniBand adapters) and selects the best algorithm for each collective operation (all-reduce, broadcast, all-gather, reduce-scatter).
NCCL supports multiple backends: NVLink for intra-machine GPU communication, InfiniBand with GPUDirect RDMA for inter-machine transfers (bypassing the CPU entirely), and TCP/IP sockets as a fallback. Setting NCCL_DEBUG=INFO reveals which topology and algorithms NCCL detected, which is essential for diagnosing performance problems.
NCCL chooses among links whose speeds differ several-fold. NVIDIA lists 600 GB/s of NVLink bandwidth for an A100 SXM against 64 GB/s for PCIe Gen4, both counting the two directions together, and a 200 Gbps InfiniBand port moves about 25 GB/s each way. When NCCL_DEBUG=INFO shows traffic falling back to sockets over a slow interface instead of InfiniBand or EFA, all-reduce can run ten times slower with no error at all, which is why that log line is the first thing to check.
Bandwidth Hierarchy
Bandwidth drops sharply at each level of a GPU cluster. Within a GPU, HBM on an A100 delivers about 2 TB/s. Between GPUs in one server, NVLink gives 600 GB/s per A100, and PCIe Gen4 about 64 GB/s. Between servers, each GPU typically gets one 100 to 400 Gbps InfiniBand or EFA link, roughly 12 to 50 GB/s, and storage over the network is slower still. The hierarchy dictates placement: tensor parallelism, which exchanges activations inside every layer, stays within the NVLink domain, while pipeline and data parallelism, which communicate less often or can overlap, span servers.
The gaps between tiers are uneven but large: HBM at about 2,000 GB/s, NVLink at 600, PCIe Gen4 at 64 and a 200 Gbps network port at 25 give ratios of roughly 3, 9 and 2.5 from one tier to the next. Time to move a tensor is size divided by bandwidth, so a 1 GB gradient bucket takes under 2 ms over NVLink but about 40 ms over one 200 Gbps port. Every design choice that keeps traffic in the faster tier saves a factor of several to dozens.
Data Pipeline Bottleneck
Network can bottleneck data loading, not just gradient synchronization. If your DataLoader fetches training data from network-attached storage (NFS, S3, GCS) slower than the GPU processes batches, the GPU sits idle waiting for data. This is especially common with large image datasets or video training data.
Solutions include: pre-downloading data to local NVMe SSDs, using data format optimizations (WebDataset, TFRecord) that enable sequential reads, increasing DataLoader workers and prefetch buffers, and using cloud instances with high network bandwidth (AWS p4d instances have 400 Gbps networking).
If loading a batch takes and computing it takes , a pipelined loader gives a step time of about , and GPU utilization falls to when loading is slower. A 100 ms compute step with a 200 ms load runs the GPU at 50 percent. On an 8-GPU p4d.24xlarge, listed at about 21.96 dollars an hour in us-east-1, that wastes about 11 dollars every hour, which is why caching to local NVMe and adding loader workers usually pays for itself.
Multipart Transfers
Object stores such as S3 scale across many connections, not within one, so large transfers are split into parts that move in parallel. boto3's TransferConfig switches to multipart above multipart_threshold, 8 MB by default, uses multipart_chunksize parts, also 8 MB, and runs max_concurrency threads, 10 by default. Downloads use byte-range requests the same way. A single TCP flow is also capped: AWS limits single-flow traffic to 5 Gbps between instances outside a cluster placement group, so one stream cannot fill a 100 Gbps instance. For large checkpoints, raise concurrency and chunk size, and spread hot objects across prefixes.
With parallel streams each limited to , throughput is about . A 10 GB checkpoint over one 5 Gbps flow needs at least 16 seconds; ten streams on a 25 Gbps link can approach 3.2 seconds. Each S3 prefix also supports at least 3,500 writes and 5,500 reads per second, so parallel parts rarely hit request limits. Beyond the link limit, more streams add only contention, which is why tuning stops where measured throughput stops rising.
Network Topology
In cloud GPU clusters, physical proximity matters. Placement groups (AWS) or compact placement policies (GCP) ensure that GPU instances are physically close — ideally on the same rack or connected to the same network switch. This minimizes inter-node latency and maximizes bandwidth.
Cross-region training (GPUs in different datacenters) is rarely practical due to WAN latency of 10-100ms versus LAN latency of 0.01-0.1ms. Even cross-availability-zone communication within a region adds measurable latency that impacts gradient synchronization.
A three-tier fat tree built from -port switches connects hosts with full bisection bandwidth: 27,648 hosts for and 65,536 for . Full bisection means any half of the hosts can talk to the other half at full link speed, but it does not make all-reduce faster as the cluster grows: ring all-reduce time per step stays near for gradient size and link bandwidth , while the latency term grows with the number of workers. Oversubscribed spines or cross-zone links make it worse.
Theory Exercise
Problem:
You are training a 7B parameter model using data parallelism across 4 nodes, each with 8 GPUs (32 GPUs total). Parameters are in fp16 (2 bytes each). The nodes are connected with 100 Gbps InfiniBand and GPUs within each node are connected with NVLink. Calculate the minimum time for one all-reduce synchronization and determine whether the network or GPU computation is the bottleneck if each training step takes 200ms of GPU computation.
Hints:
- Calculate gradient size: 7 billion parameters * 2 bytes = 14GB per GPU
- Ring all-reduce transfers 2*(N-1)/N * gradient_size per GPU, but inter-node communication is limited to InfiniBand speed
- Consider that intra-node all-reduce (8 GPUs via NVLink) is essentially free compared to inter-node