131 Matching Annotations
  1. Last 7 days
    1. Takeaway: when performing an AllGather (or a ReduceScatter or AllReduce) in a throughput-bound regime, the actual communication time depends only on the size of the array and the available bandwidth, not the number of devices over which our array is sharded!

      ok nice, this means that we could (if ICI keeps constant) scale up the number of shards arbitrarily, and therefore have arbitrarily large arrays? wait wait wiat no n ono no nono, as we still need to be able to keep the weights on any one tpu at once awww manne

    2. Note that this doesn’t depend on X! That’s kind of striking, because it means even though our TPUs are only locally connected, the locality of the connections doesn’t matter. We’re just bottlenecked by the speed of each link.

      wow! wowowowoowow o ok oko ok ok

    3. What is an AllGather? An AllGather is the first core MPI communication primitive we will discuss. An AllGather removes the sharding along an axis and reassembles the shards spread across devices onto each device along that axis. Using the notation above, an AllGather removes a subscript from a set of axes, e.g. AllGatherXY(A[IXY,J])→A[I,J] We don’t have to remove all subscripts for a given dimension, e.g. A[IXY,J]→A[IY,J] is also an AllGather, just over only a single axis. Also note that we may also wish to use an AllGather to remove non-contracting dimension sharding, for instance in the matrix multiply: A[IX,J]⋅B[J,K]→C[I,K] We could either AllGather A initially to remove the input sharding, or we can do the sharded matmul and then AllGather the result C. How is an AllGather actually performed? To perform a 1-dimensional AllGather around a single TPU axis (a ring), we basically have each TPU pass its shard around a ring until every device has a copy.A GPU AllGather can also work like this, where you create a ring out of the GPUs in a node and pass the chunks around in that (arbitrary) order. Here is an animation:

      allgather removes sharding along an axis copies shards spread across devices onto each device by (for a 1d allgather) has each TPU pass its shard around a ring until all have a copy - steal this animation for me.

      so i guess that for this thing, we should have 2-3 cards one showing WHY the allgather is needed i.e showing hte mats spread across things and how they dont line up, and then (perhaps on the same card) the animation of each TPU getting the full copy of the mat, and then showing hte mat performed

      and then another card showing the animation of how the allgather is performed i.e apssing it around in a ring or something.

      perhaps these could be combined into one card?? would be interesting to think about

    4. Takeaway: When multiplying matrices where one of the matrices is sharded along the contracting dimension, we generally AllGather it first so the contraction is no longer sharded, then do a local matmul.

      animation might be good for this something like:

      shows the activations / weights sharded per thing, and shows how the mats dont like up for a clean mul

      then the allgather to sync up so all of them have the same things (perhaps with not perfect replication or something) and then show the mats line up for a clean mul

    5. A[I,JX]⋅B[J,K]→C[I,K] We cannot simply multiply the local chunks of A and B because we need to sum over the full contracting dimension of A, which is split across the X axis. Typically, we first “AllGather” the shards of A so every device has a full copy, and only then multiply against B:

      diagram for ts pls,

    6. Because neither A nor B has a sharded contracting dimension J, we can simply perform the local block matrix multiplies of the inputs and the results will already be sharded according to the desired output shardings. When both multiplicands have non-contracting dimensions sharded along the same axis, this is no longer true (see the invalid shardings section for details).

      yea id like a diagram for this (that has a compact form of the notation for the activations [i,j] dot batches [j,k] i.e showing the configs for whihc this is the case (concisely) something like that shows that hte J inner dims are unchanged /not sharded, while the other things can be all different random things (althoguh not hte same)

    7. Lemma: when multiplying sharded matrices, the computation is valid and the output follows the sharding of the inputs unless the contracting dimension is sharded or both matrices are sharded along the same axis. For example, this works fine

      yea i mean the diagrams that you made should show this idea, there should be some visual intuition for this.

      i.e if you shard contracting dim, then you need to send partial products to each other before doing next step or something idk

    8. Conveniently, we can boil down all possible shardings into roughly 4 cases we need to consider, each of which has a rule for what communication we need to add Case 1: neither input is sharded along the contracting dimension. We can multiply local shards without any communication. Case 2: one input has a sharded contracting dimension. We typically “AllGather” the sharded input along the contracting dimension. Case 3: both inputs are sharded along the contracting dimension. We can multiply the local shards, then “AllReduce” the result. Case 4: both inputs have a non-contracting dimension sharded along the same axis. We cannot proceed without AllGathering one of the two inputs first.

      i dont think that it is particularly useful to memorise the: 'what are the rough possible shardings .. ' idt that has recall usefulness.

    9. Each sharding will involve different communication

      yea like i said earlier, id like the following shardings and then the diagrams for them, which show why things are the way they are with respect to the dims etc, and the contracting dim

    10. We illustrate the other possibilities in the figure below:

      yea just take these for me, hmmmm i guess that this would be pretty insane right. so for the sharding we have 9 possible arrangements for the sharding hmmmmm and so if we have 9 * 9 shardings for the weights and activations. that is not reasonalbe to show. then just show the typical ones that are used (i.e the possible configs that are mentioned in this post) and then the visual intution for why we need the allgather

    11. We can indicate that one of these dimensions has been partitioned across a mesh axis with a subscript mesh axis. For instance A[I_X, J] would mean that the I logical axis has been partitioned across the X mesh dimension, but that the J dimension is not partitioned, and the blocks remain partially-replicated across the Y mesh axis.

      id like a set of diagrams for these. i have the rotation type view of doing matmuls, and so i want diff configs of sharding, (i.e the four casese below) and then show how the layout determines the constriants/ the behaviour( i.e showing the partial matmuls and stuff) make sure dims are assymetrical (take the diagrams from this page for me) i.e show me how if we have the activations split like this, the different configs we have for sharding the weights, and what that means ... idk if that makes sense

    12. Sharding: A[I_X, J_Y], which tells us to shard the first axis, I, along the mesh axis X, and the second axis, J, along the mesh axis Y. This sharding tells us that each shard holds 1 / (\lvert X\rvert \cdot \lvert Y\rvert) of the array.

      array [ i, j ] with subscripts = id like a diagram for htis:

      have some placeholder for the subscripts or something, and then show the diff configs visually depeneding on the assignment or something idk, if it is possible, then id like to see all different arrangement configs on the same grid or something (make the TPU grid assymetic in dims ( and 2d i.e two diff sized length and width)

    13. We use a variant of named-axis notation to describe how the tensor is sharded in blocks across the devices: we assume the existence of a 2D or 3D grid of devices called the device mesh where each axis has been given mesh axis names e.g. X, Y, and Z. We can then specify how the matrix data is laid out across the device mesh by describing how each named dimension of the array is partitioned across the physical mesh axes. We call this assignment a sharding.

      device mesh 2/3d grid of devices

      each axis names xyz

      sharding - mat data laid out across device mesh

    14. Note how the sharded array still has the same global or logical shape as the unsharded array, say (4, 128), but it also has a device local shape, like (2, 64), which gives us the actual size in bytes that each TPU is holding (in the figure above, each TPU holds ¼ of the total array). Now we’ll generalize this to arbitrary arrays.

      global/logical shape is shape of the arrays/ mats to be mulled

      device local shape is the the shape of the bytes that the TPU is holding.

    1. The VPU is the TPU’s vector arithmetic core. The VPU consists of a two dimensional SIMD vector machine (the VPU) that performs elementwise arithmetic operations like vadd (vector addition) or vmax (elementwise max) and a set of vector registers called VREGs that hold data for the VPU and MXU.

      diagram of this<br /> + ops that it does

    2. The scalar core is the control unit of the TPU. It fetches and dispatches all instructions and executes transfers from HBM into VMEM, and can be programmed to do scalar metadata work. Because the scalar core is single-threaded, one side-effect of this is that each core of the TPU is only capable of creating one DMA request per cycle. To put this in context, a single scalar core controls a VPU (consisting of 4096 ALUs), 4 MXUs, 2 XLUs, and multiple DMA engines. The highly skewed nature of control per unit compute is a source of hardware efficiency, but also limits the ability to do data dependent vectorization in any interesting way.

      control core fetches and dispatches instructions executes transfers from HBM into VMEM scalar metadata work single thread one DMA (what does that mean) per cycle

      control hierarchy diagram (showing what instructions go where)

    3. All lanes and sublanes execute the same program every cycle in a pure SIMD manner, but each ALU can perform a different operation. So we can e.g. process 1 vadd and 1 vsub in a single cycle, each of which operates on two full VREGs and writes the output to a third.

      and this

    4. VPU: The VPU is a 2D vector arithmetic unit of shape (8, 128) where the 128 dimension is referred to as lane axis and the dimension of 8 is referred to as the sublane axis. Each (lane, sublane) pair on v5 contains 4 standard floating-point ALUs which are independent of each other. The VPU executes most arithmetic instructions in one cycle in each of its ALUs (like vadd or vector add) with a latency of 2 cycles, so e.g. in v5 you can add 4 pairs of f32 values together from VREGs in each cycle. A typical VPU instruction might look like {v2 = vadd.8x128.f32 v0, v1} where v0 and v1 are input VREGs and v2 is an output VREG.

      vec arithmetic unit of shape (8,128) naming conventions (sublane and lane) typical latency and operation time instructions type

    5. VREGs: Each TPU v5p core has 64 32-bit VREGs (32 in TPU v4), giving us a total of about 64 * 8 * 128 * 4 = 256kB of VREG memory per core (or 2x this for the whole chip since we have two cores). A TPU v5p can load 3 registers from VMEM each cycle, and write 1 register to VMEM each cycle.

      do only the facts invariant of specific TPU things (or give the midpoint of the band) include diagram on the layout

    6. Key Takeaways TPUs are simple and can in most cases be thought of as a matrix multiply unit connected to memory (super fast), other chips over ICI (rather fast), and the rest of the datacenter over DCN (somewhat fast). Communication is limited by our various network bandwidths in order of speed: HBM bandwidth: Between a TensorCore and its associated HBM. ICI bandwidth: Between a TPU chip and its nearest 4 or 6 neighbors. PCIe bandwidth: Between a CPU host and its associated tray(s) of chips. DCN bandwidth: Between multiple CPU hosts, typically hosts not connected by ICI. Within a slice, TPUs are only connected to their nearest neighbors via ICI. This means communication over ICI between distant chips in a slice needs to hop over the intervening chips first. Weight matrices need to be padded to at least size 128 (256 on TPU v6e) in both dimensions to fill up the MXU (in fact, smaller axes are padded to 128). Lower precision matrix multiplication tends to be faster. TPUs can do int8 or int4 OPs roughly 2x/4x faster than bfloat16 FLOPs for generations that support it. VPU operations are still performed in fp32. To avoid bottlenecking the TPU compute unit, we need to make sure the amount of communication across each channel is proportional to its speed.

      just use these for some directions (alongside/ the other notes should be folded into these as the source of truth.

    7. ICI is very fast relative to DCN, but is still slower than HBM bandwidth. For instance, a TPU v5p has: 2.8e12 bytes/s (2.8 TB/s) of HBM bandwidth per chip. 9e10 bytes/s (90 GB/s) of ICI bandwidth per axis, with 3 axes per chip.The page above lists 100 GB/s of bandwidth, which is slightly different from what's listed here. TPU ICI links have slightly different bandwidths depending on the operation being performed. You can generally use the numbers in this doc without worry. 6.25e9 bytes/s (6.25 GB/s) of DCN (egress) bandwidth per TPU (via 1-2 NICs on each host).

      just one thing showing memory bandwith/latency hierarchies

    8. This nearest-neighbor connectivity is a key difference between TPUs and GPUs. GPUs are connected with a hierarchy of switches that approximate a point-to-point connection between every GPU, rather than using local connections like a TPU. Typically, GPUs within a node (8 GPUs for H100 or as many as 72 for B200 NVL72) are directly connected, while larger topologies require O(log(N)) hops between each GPU. On the one hand, that means GPUs can send arbitrary data within a small number of hops. On the other hand, TPUs are dramatically cheaper (since NVLink switches are expensive), simpler to wire together, and can scale to much larger topologies because the number of links per device and the bandwidth per device is constant. Read more here.

      diagram difference between hierarchical structures showing how GPUs would have to traverse the depth of the binary tree vs how a TPU has to just go the distance

    9. TPU pods (connected by ICI) can get really big: the maximum pod size (called a superpod) is 16x16x16 for TPU v4 and 16x20x28 for TPU v5p. These large pods are composed of reconfigurable cubes of 4x4x4 chips connected by optical wraparound linksThe optical switch is simply a reconfigurable connection with the same ICI bandwidth. It just lets us connect cubes while retaining a wraparound link. that we can reconfigure to connect very large topologies. Smaller topologies (e.g. 2x2x1, 2x2x2) can also be requested, albeit with no wraparounds. This is an important caveat, since it typically doubles the time of most communication. Any multiple of a full cube (e.g. 4x4x4 or 4x4x8) will have wraparounds provided by the optical switches.

      just add the standard sizes for superpodsizes and that smaller ones would not allow for wraparounds due to hte structure or something, this could have a good diagram for it

    10. The toroidal structure reduces the maximum distance between any two nodes from N to N / 2, making communication much faster. TPUs also have a “twisted torus” configuration that wraps the torus in a Mobius-strip like topology to further reduce the average distance between nodes.

      show how this is the case with diagram

    11. PCIe bandwidth is limited: Like the HBM \leftrightarrow VMEM link, the CPU \leftrightarrow HBM PCIe connection has a specific bandwidth that limits how quickly you can load from host memory to HBM or vice-versa. PCIe bandwidth for TPU v4 is 16GB / second each way, for example, so close to 100x slower than HBM. We can load/offload data into the host (CPU) RAM, but not very quickly.

      something on the relative diffs or the bandwith diffs between different memory things, i.e HBM ICI VMEM PCIe etc with some nice diagram

    12. Chips are connected to each other through the ICI network in a Pod. In older generations (TPU v2 and TPU v3), inference chips (e.g., TPU v5e), and Trillium (TPU v6e), ICI (“inter-chip interconnects”) connects the 4 nearest neighbors (with edge links to form a 2D torus). TPU v4 and TPU v5p are connected to the nearest 6 neighbors (forming a 3D torus). Note these connections do not go through their hosts, they are direct links between chips.

      ici older chips / inference chips ˙ending with an e' have 2d toroidal structure for four nearest neighbors (incude info like v3 and prev3 , and inference ) others are 3d torus

      do not go through hosts, i.e they have their own bandwith things, and are not via PCIe

    13. Chips are arranged in sets of 4 on a ‘tray’ connected to a CPU host via PCIe network. This is the format most readers will be familiar with, 4 chips (8 cores, though usually treated as 4 logical megacores) exposed through Colab or a single TPU-VM. For inference chips like the TPU v5e, we have 2 trays per host, instead of 1, but also only 1 core per chip, giving us 8 chips = 8 cores.

      per tray four chips one cpu host via PCIe net 8 cores, or 4 logical megacores sometimes 2 trays per host for megacore chips for a constant of 8 cores per tray

    14. Key takeaway: TPUs are very simple. They load weights from HBM into VMEM, then from VMEM into a systolic array which can perform around 200 trillion multiply-adds per second. The HBM \leftrightarrow VMEM and VMEM \leftrightarrow systolic array bandwidths set fundamental limits on what computations TPUs can do efficiently.

      yea include this

    15. Generally, all TPU operations are pipelined and overlapped. To perform a matmul X \cdot A \to Y, a TPU would first need to copy chunks of matrices A and X from HBM into VMEM, then load them into the MXU which multiplies chunks of 8x128 (for X) and 128x128 (for A), then copy the result chunk by chunk back to HBM. To do this efficiently, the matmul is pipelined so the copies to/from VMEM are overlapped with the MXU work. This allows the MXU to continue working instead of waiting on memory transfers, keeping matmuls compute-bound, not memory-bound.

      include something on this, alongside the diagram , though [it is difficult to visualise this without the animation (that is a cool animation) ]

    16. HBM (High Bandwidth Memory) is a big chunk of fast memory that stores tensors for use by the TensorCore. HBM usually has capacity on the order of tens of gigabytes (for example, TPU v5e has 16GiB of HBM). When needed for a computation, tensors are streamed out of HBM through VMEM (see below) into the MXU and the result is written from VMEM back to HBM. The bandwidth between HBM and the TensorCore (through VMEM) is known as “HBM bandwidth” (usually around 1-2TB/sec) and limits how fast computation can be done in memory-bound workloads.

      hbm capcity 10s of gbs flow of hbm -> VMEM -> MXU -> VMEM -> HBM or something like that, perhaps we just need two faced arrows bandwith

    17. The diagram above also includes a few other components like SMEM and the scalar unit, which are used for control flow handling and are discussed briefly in Appendix A, but aren’t crucial to understand. On the other hand, HBM is important and fairly simple:

      include information on SMEM in the same manner as the other components ( i think that they are important - how can you understand a system if not at least knowing of every one of its components )

    18. VMEM (Vector Memory) is an on-chip scratchpad located in the TensorCore, close to the compute units. It is much smaller than HBM (for example, 128 MiB on TPU v5e) but has a much higher bandwidth to the MXU. VMEM operates somewhat like an L1/L2 cache on CPUs but is much larger and programmer-controlled. Data in HBM needs to be copied into VMEM before the TensorCore can do any computation with it.

      diagram + size rel to HBM (i.e scaling factor and also OOM difference i.e HBM has 10s gbs capacity, how much does VMEM have) programmer controlled cache

    19. he VPU (Vector Processing Unit) performs general mathematical operations like ReLU activations or pointwise addition or multiplication between vectors. Reductions (sums) are also performed here. Appendix A provides more details.

      diagram + same structure as mxu concise

    20. The MXU (Matrix Multiply Unit) is the core of the TensorCore. For most TPU generations, it performs one bf16[8,128] @ bf16[128,128] -> f32[8,128] matrix multiplyTPU v6e (Trillium) has a 256x256 MXU, while all previous generations use 128x128. every 8 cycles using a systolic array (see Appendix B for details). This is about 5e13 bf16 FLOPs/s per MXU at 1.5GHz on TPU v5e. Most TensorCores have 2 or 4 MXUs, so e.g. the total bf16 FLOPs/s for TPU v5e is 2e14. TPUs also support lower precision matmuls with higher throughput (e.g. each TPU v5e chip can do 4e14 int8 OPs/s).

      diagram + core details ( i.e matmul /8 cycles) (not local to this card, but idea / derivation on why lower precision matmuls can often be done quicker (i.e despite flopcount remaining hte same, for half preciison, you can do double flops/s

    1. Takeaway: for a bfloat16 matmul to be compute-bound on most TPUs, we need our per-replica token batch size to be greater than 240.Note that this is _not_ the batch size in the usual sense, where it means the batch size in sequences. It turns out most rooflines depend purely on the number of tokens, whether they belong to the same or different sequences. For instance if you have a batch size of 512 sequences of 4096 tokens on 2048 GPUs, you have a total batch size of `512 * 4096 = 2M` tokens, and a local batch size of 1k tokens.

      also good to have : just include all takeaways a

    2. replica) batch size B < 1024 tokens (not sequences) but D and F > 8000. Thus we generally become compute-bound when our per-replicaWe say per-replica because, if we do some kind of model sharding to increase the number of chips used in the matmul, we scale both our available compute and memory bandwidth by the same amount. Thus the critical batch size is true per independent copy of the model weights. batch size is greater than 240 tokens, a very simple rule!

      include this as reasoning for the prev note

    3. hape \text{bf16}[B, D],

      include informantion about how bf16 is different from fp16 (make it visual, i.e show the bits assigned to what fields and such) \

    4. Example (dot product)

      re this, dont bother with this precise example, but part of this generalises and is used for matmuls (i.e fetching costs and the flop cost) so you should add those things right? and show how for large n it is just 2N for the flopcost

    5. That means if an algorithm has a lower arithmetic intensity than 240 FLOPs/byte, it will be bound by byte loading and thus we won’t make good use of our hardware.This is only true if the algorithm loads its weights from HBM and runs in the MXU. As we'll discuss in the next section, we can sometimes store parameters in VMEM which has a much higher bandwidth. Many algorithms also run in the VPU, which has different performance characteristics. Let’s look at one such example:

      yea re the previous not, it was precisely this

    6. The quantity \text{Intensity}(\text{Accelerator}) is the arithmetic intensity at which our accelerator achieves its peak FLOPs/s.

      something on this (i.e showing the usage of comparing algo intensity and hardware intensity, or something like that)

    7. Definition: the arithmetic intensity of an algorithm is given by the ratio of the total FLOPs it performs to the number of bytes it needs to communicate — either within a chip or between chips.

      add this

    8. we can lower-bound training and inference time by using the maximum of computation and communication time. We can also upper-bound with their sum

      write the latex thing for lwoer bound and upper bound for this with Tmath and Tcomms as the time for each thing respectively)

    9. What is actually happening within the model that takes substantial time and how long should we expect it to take?

      perhaps something small like computation, within chip comms, and interchip comms or something. (+ some other details, like from where to where, the protocols, like DCN or ICI or whatever)

  2. Jun 2026
    1. fun (s, e) ->if s >= !cur_end then beginincr count;cur_end := eend

      unpacks a tuple if the starting time is greater than the current current ending time, then it increases the count box and then updates the current end time to be the end time of this, and then i suppose that it would move on.

      ahhh so it is doing something like: you have all of your events lined up by their end times, and you start out at the beginning with having a super bad end time (min int) and so at that point you pick the best thing that is better than that (as you satisfy the check for you being greater than teh dereferenced curend)

      and then after that you move to the next point of the array (and you cna only consider new events if the start time is greater (after) the current ending time of the current event. and then for each time you pick something, you increase your count

    Annotators

    1. Write a function to compute the sum of the elements in an integer array.

      let rec summary x i acc =

      match x with x when Array.length x <= i -> acc | _-> summary x (i+1) (acc + x.(i)) ;; val summary : int array -> int -> int -> int = <fun>

      let sum x = summary x 0 0 ;;

      val sum : int array -> int = <fun>

    2. Imagine that the for … = … to … do … done construct did not exist. How might we create the same behaviour?

      soem recursion where we would decrement some counter by 1 or something or increase until it hit some value

    3. What references have been created? What are their initial and final values after this expression has been evaluated? What is the type of this expression?

      1 2, then 2 2 and then 2 4 and then evals the term 2 + 4 which is 6

    1. It's like the firing of the neurons is going only in one direction.

      is this not what humans do? hebbian plasticity?

      i thought that the interesting part of human vs deep learning nets was that NNs went in BOTH directions (backprop + feed forward) whereas humans only went in one direction

    1. And we can bring common tasks into distribution quite easily through RL and SFT.

      some tasks may never enter the distribution; we retain our humanity !! nice

    2. Blind/deaf people

      this would be an interesting blog post: on the topic of sample efficiency in humans it seems so odd that they take in dramatically less information, and yet have the cognitive capabilities of those without disabilities

    3. Many billions of years of evolution is our pre-training

      argument : pretraining includes all of human evolution rather than just what we learn in our lifetimes.

      this is less of something like 'they will eventually be as good as us' or something like that, but more 'its super hard, and its impresive that they have come as far as they have' which is a pessimistic of ML.

    1. writing a dictionary of type (int × string) to a channel

      so what is this doing, it is taking in ch and a dictionary pair, and then it is calling the output string with the channel as a parameger and the int value of k, and then it is prinving a newline, and then it is printing the string of teh other value pair of it, and then it is outputting a newline. so output char takes in ch as its location arguemnt, and takes in /n a char for its other on , while output string does hte same thing, but hmmm how does it know how to update the lcoainot, like how does it konw that it needs otb e at that position in the thing> i guess it woudld mean that the output _(something) funciton always takes hte pointer for the bottom of the page, rather htan anything else, and simply writes underneath it.

      and then the dictionary to channel would simply iterate the list of pairs and write it to hte out channel and then output hte unit (which shows that teh function doesnt compute anyhting and return it, it just pastes the text in taht position.

    1. Rewrite the summary paragraph at the end of this chapter for the three argument function g a b c.

      let f = fun a -> fun b -> fun c -> something

    2. Why can we not write a function to halve all the elements of a list like this: map (( / ) 2) [10; 20; 30]? Write a suitable division function which can be partially applied in the manner we require.

      let div x = fun y -> y / x ;;

      and then we do

      map (div 2) [list ]

    3. member_all x ls

      so it would be something of: a' -> b' list list -> bool and the logic would be to do the double nested loop like you did with the other thing.

    4. Write a function truncate which takes an integer and a list of lists, and returns a list of lists, each of which has been truncated to the given length. If a list is shorter than the given length, it is unchanged. Make use of partial application.

      so it is int - > list list -> list list lets just pass take wiht the int value, and sublist, and then pass it for all of them . so it would be let rec truncate a l = match l with h :: t -> take a h :: truncate a l | [] -> [] . .or something like that

    5. type of member x

      ermermermermermermermermermemremr i guess that it would be something like list -> bool or something idk. let me think, the whole hting is a' -> b ' list -> bool. so it would be a' -> ( b' list -> bool) and if i am passing the first thing, then it woudl jsut take in the b' list and output a bool

    1. Write the function union a b which forms the union of two dictionaries. The union of two dictionaries is the dictionary containing all the entries in one or other or both. In the case that a key is contained in both dictionaries, the value in the first should be preferred.

      let rec union a b = match a , b with (ka, va) :: ta , (kb, vb) :: tb when ka = kb -> (ka, va) :: union ta tb |ha :: ta , hb :: tb -> ha :: hb :: union ta tb<br /> | or something like that

    1. Write another function which uses the previous one, but handles the exception, and simply returns zero when a suitable integer cannot be found.

      yea

    2. Write an exception definition and a function which calculates the largest integer smaller than or equal to the square root of a given integer. If the argument is negative, the exception should be raised.

      messy and i dont think i got this good

      cycled from zero with square y < x else while greater than zero -> y -1 else raise negative you could also do

    1. Write the function for_all which, given a function of type α → bool and an argument list of type α list evaluates to true if and only if the function returns true for every element of the list. Give examples of its use.

      let rec forall x y = match y with h :: t -> x h && forall x t | [] -> true ;;

    2. Write a function filter which takes a function of type α → bool and an α list and returns a list of just those elements of the argument list for which the given function returns true.

      let rec filter x y = match y with h :: t when x h -> h :: filter x t | h :: t -> filter x t |_-> []

    3. Write a simple recursive function calm to replace exclamation marks in a char list with periods. For example calm [’H’; ’e’; ’l’; ’p’; ’!’; ’ ’; ’F’; ’i’; ’r’; ’e’; ’!’] should evaluate to [’H’; ’e’; ’l’; ’p’; ’.’; ’ ’; ’F’; ’i’; ’r’; ’e’; ’.’]. Now rewrite your function to use map instead of recursion. What are the types of your functions?

      this is in curly quotes for some reason. it should be:

      ['H'; 'e'; 'l'; 'p'; '!'; ' '; 'F'; 'i'; 'r'; 'e'; '!']

      and eval to ['H'; 'e'; 'l'; 'p'; '.'; ' '; 'F'; 'i'; 'r'; 'e'; '.']

    1. We mentioned that the comparison functions like  <  work for many OCaml types. Can you determine, by experimentation, how they work for lists? For example, what is the result of [1; 2] < [2; 3]? What happens when we sort the following list of type char list list? Why?

      first to differ + if extended it is greater tha n

    1. Can you explain why the rev function we defined is inefficient? How does the time it takes to run relate to the size of its argument? Can you write a more efficient version using an accumulating argument? What is its efficiency in terms of time taken and space used?

      o(n) i think for normal rev, and

    2. Use your member function to write a function make_set which, given a list, returns a list which contains all the elements of the original list, but has no duplicate elements. For example, make_set [1; 2; 3; 3; 1] might return [2; 3; 1]. What is the type of your function?

      honestly embarassing how long this took me jesus

    3. Write a function evens which does the opposite to odds, returning the even numbered elements in a list. For example, evens [2; 4; 2; 4; 2] should return [4; 4]. What is the type of your function?

      took me a long time to figure out this was just odds with an offset. just build odds and then call it from evens.

    4. Write a function which, given a list, builds a palindrome from it. A palindrome is a list which equals its own reverse. You can assume the existence of rev and @. Write another function which determines if a list is a palindrome.

      yea this was fine. build rev first, and then do list @ rev list

    5. Write a function drop_last which returns all but the last element of a list. If the list is empty, it should return the empty list. So, for example, drop_last [1; 2; 4; 8] should return [1; 2; 4]. What about a tail recursive version?

      this was quite tricky.

      i think the difficulty is starting to ramp up

    6. Write a function count_true which counts the number of true elements in a list. For example, count_true [true; false; true] should return 2. What is the type of your function? Can you write a tail recursive version?

      yup nice one

    1. There is a special pattern x..y to denote continuous ranges of characters, for example ’a’..’z’ will match all lowercase letters. Write functions islower and isupper, each of type char → bool, to decide on the case of a given letter.

      i wonder if i need a typecheck or something

    2. For each of the previous three questions, comment on whether you think it is easier to read the function with or without pattern matching. How might you expect this to change if the functions were much larger?

      lowk think it was nicer recursively (or at least more compact) but i guess that it is easier to check cases and conditions

    3. Rewrite the not function from the previous chapter in pattern matching style. Use pattern matching to write a recursive function which, given a positive integer n, returns the sum of all the integers from 1 to n. Use pattern matching to write a function which, given two numbers x and n, computes xn.

      nice,

    1. Can you suggest a way of preventing the non-termination of the factorial function in the case of a zero or negative argument?

      some elseif statements (first check for lessequal zero

    2. Write a function isconsonant which, given a lower-case character in the range ’a’…’z’, determines if it is a consonant.

      let isconsonant j = if j = (OR all of the vowels) then false, else true.

    3. Write a recursive function which, given a number n, calculates the sum 1 + 2 + 3 + … + n. What is its type?

      i guess that you could also do gauss method for this but it would be a little more finicky. a bit better for time complexity i think

    4. Write a function which returns true if both of its arguments are non-zero, and false otherwise. What is the type of your function?

      int -> int -> bool

    5. # let rec gcd a b = if b = 0 then a else gcd b (a mod b);; val gcd : int -> int -> int = <fun> # gcd 64000 3456;; - : int = 128 Here is the evaluation:

      holy how did i not think of that

    6. what if we try to evaluate factorial (-1)?

      yupyup what is also interesting is that if you put in a superlarge value, it defaults to zero rather than the maxvalue - i wonder why that is?

    1. What is the effect of the comparison operators like < and > on alphabetic values of type char? For example, what does ’p’ < ’q’ evaluate to? What is the effect of the comparison operators on the booleans, true and false?

      bool, and counts the position in the alphabet.

      true > false and false < true, and true = true = true and false = false = true, i.e things are themselves, are not greater or less than themselves, and true is greater than false and false is smaller than true

    2. Why not just use, for example, the integer 0 to represent false and the integer 1 for true? Why have a separate bool type at all?

      perhaps people would get confused between whether something was an int or a bool or sumn idk bro

    3. Can you discover what the mod operator does when one or both of the operands are negative? What about if the first operand is zero? What if the second is zero?

      the mod is negative too. if it is (any int) mod (nonzero int) it will always compute, but not if mod operand is 0

    4. The range of numbers available is limited. There are two special numbers: min_int and max_int. What are their values on your computer? What happens when you evaluate the expressions max_int + 1 and min_int - 1?

      max is this number 4611686018427387903 and min is htis -4611686018427387904

      and it sums to -1

    5. A programmer writes 1+2 * 3+4. What does this evaluate to? What advice would you give him?

      11, but in the case that he wanted to make it 21, then he should add parentheses around the expressions he wants to take precedence

    6. Consider the evaluations of the expressions 1 + 2 mod 3, (1 + 2) mod 3, and 1 + (2 mod 3). What can you conclude about the + and mod operators?

      that mod takes precedence over add

    1. But once that company is past the VC-funded hyper growth phase and wants to maximize its profits, it will end up with a multi-thousand person platforms org, just like Google's, unless the company wants to leave hundreds of millions or billions of dollars a year on the table due to hardware and software inefficiency.

      hmmmmmm

    1. nd how much that would then change our de­ci­sions or ac­tions

      this means that it would take into account the importance of the article itself, which doesnt seem right. i could do a criticism of some study also thinking that the outcome of it does not matter - if it was true i would not care, and the same if it was not