Class: Toy::Card

Inherits:
Object
  • Object
show all
Defined in:
lib/toy/dev/toy_card.rb

Overview

A complete algorithm card.

c = Toy::Card.new("Toy::GPT2.forward(x, p_start)", "HF GPT-2 family")
c.add_input("x", "{1..V}^T", "token IDs")
c.add_output("P", "R^{T×V}", "logits")
c.add_hyper("V", cfg.vocab.to_s)
c.add_param("W_e", "R^{V×D}", "")
c.add_param_extra("(total " + Toy.fmt_count(param_count) + ", tied)")
c.step_bind("e", "W_e[x] + W_p[p_start : p_start+T]", "e ∈ R^{T×D}")
c.step_loop("ℓ ← 1, …, N", "")
c.step_update("e", "e + Attn(LN(e; γ_ℓ^1, β_ℓ^1, ε); θ_ℓ^attn)", "e ∈ R^{T×D}", "")
c.step_loop_close
c.step_return("P")
puts c.render_pseudocode

Constant Summary collapse

SHAPE_COL =

target column for the shape annotation

70

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name_, family) ⇒ Card

Returns a new instance of Card.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/toy/dev/toy_card.rb', line 105

def initialize(name_, family)
  @name_  = name_
  @family = family

  # Seed-then-pop pins each Array as PtrArray<ConcreteRow> in
  # Spinel's type inference. Without the seed it would be
  # untyped-empty and the first push would lock it in incorrectly.
  @inputs = [Toy::CardItem.new("", "", "")]
  @inputs.pop
  @outputs = [Toy::CardItem.new("", "", "")]
  @outputs.pop
  @hypers = [Toy::CardHyper.new("", "")]
  @hypers.pop
  @params = [Toy::CardItem.new("", "", "")]
  @params.pop
  @param_extras = [""]
  @param_extras.pop
  @steps = [Toy::Step.new("bind", "", "", "", "")]
  @steps.pop
end

Instance Attribute Details

#familyObject

Returns the value of attribute family.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def family
  @family
end

#hypersObject

Returns the value of attribute hypers.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def hypers
  @hypers
end

#inputsObject

Returns the value of attribute inputs.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def inputs
  @inputs
end

#name_Object

Returns the value of attribute name_.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def name_
  @name_
end

#outputsObject

Returns the value of attribute outputs.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def outputs
  @outputs
end

#param_extrasObject

Returns the value of attribute param_extras.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def param_extras
  @param_extras
end

#paramsObject

Returns the value of attribute params.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def params
  @params
end

#stepsObject

Returns the value of attribute steps.



101
102
103
# File 'lib/toy/dev/toy_card.rb', line 101

def steps
  @steps
end

Class Method Details

.pad_to(body, col, tail) ⇒ Object

Right-pad ‘body` to `col` characters then append `tail` — only if `tail` is non-empty. Used to align shape annotations.



175
176
177
178
179
180
181
182
183
# File 'lib/toy/dev/toy_card.rb', line 175

def self.pad_to(body, col, tail)
  return body if tail == ""
  n = body.length
  if n >= col
    body + "   " + tail
  else
    body + (" " * (col - n)) + tail
  end
end

Instance Method Details

#add_hyper(key, value) ⇒ Object



136
137
138
# File 'lib/toy/dev/toy_card.rb', line 136

def add_hyper(key, value)
  @hypers.push(Toy::CardHyper.new(key, value))
end

#add_input(name_, type_, note) ⇒ Object

— builder API ————————————————–



128
129
130
# File 'lib/toy/dev/toy_card.rb', line 128

def add_input(name_, type_, note)
  @inputs.push(Toy::CardItem.new(name_, type_, note))
end

#add_output(name_, type_, note) ⇒ Object



132
133
134
# File 'lib/toy/dev/toy_card.rb', line 132

def add_output(name_, type_, note)
  @outputs.push(Toy::CardItem.new(name_, type_, note))
end

#add_param(name_, type_, note) ⇒ Object



140
141
142
# File 'lib/toy/dev/toy_card.rb', line 140

def add_param(name_, type_, note)
  @params.push(Toy::CardItem.new(name_, type_, note))
end

#add_param_extra(line) ⇒ Object

Free-text continuation lines under “Param:” (e.g. “(total Xxx)”).



145
146
147
# File 'lib/toy/dev/toy_card.rb', line 145

def add_param_extra(line)
  @param_extras.push(line)
end

#hyper(key) ⇒ Object

Look up a hyper by key. Returns “” if absent — used by the round-trip emitter (prep/card_to_code.rb) so it can read the IR directly instead of regexing the rendered text.



292
293
294
295
296
297
# File 'lib/toy/dev/toy_card.rb', line 292

def hyper(key)
  @hypers.each do |h|
    return h.value if h.key == key
  end
  ""
end

#render_pseudocodeObject



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/toy/dev/toy_card.rb', line 187

def render_pseudocode
  s = "Algorithm: " + @name_
  if @family != ""
    # Tag in square brackets, padded with a few spaces.
    pad = "      "
    s = s + pad + "[" + @family + "]"
  end
  s = s + "\n"

  # Input / Output / Hyper / Param headers.
  first = true
  @inputs.each do |it|
    prefix = first ? "  Input:    " : "            "
    line = prefix + it.name_ + "" + it.type_
    if it.note != ""
      line = line + "   (" + it.note + ")"
    end
    s = s + line + "\n"
    first = false
  end

  first = true
  @outputs.each do |it|
    prefix = first ? "  Output:   " : "            "
    line = prefix + it.name_ + "" + it.type_
    if it.note != ""
      line = line + "   (" + it.note + ")"
    end
    s = s + line + "\n"
    first = false
  end

  if @hypers.length > 0
    line = "  Hyper:   "
    @hypers.each do |h|
      line = line + " " + h.key + "=" + h.value
    end
    s = s + line + "\n"
  end

  first = true
  @params.each do |it|
    prefix = first ? "  Param:    " : "            "
    line = prefix + it.name_ + "" + it.type_
    if it.note != ""
      line = line + "   (" + it.note + ")"
    end
    s = s + line + "\n"
    first = false
  end
  @param_extras.each do |line|
    s = s + "            " + line + "\n"
  end

  # Body: walk steps, track step number and indent depth.
  n = 1
  depth = 0
  @steps.each do |st|
    kind = st.kind
    if kind == "loop_close"
      depth = depth - 1
    end
    indent = "   " * depth
    head = "  " + n.to_s.rjust(2) + ": " + indent
    body = ""

    if kind == "bind" || kind == "update"
      body = head + st.var + "" + st.expr_str
    elsif kind == "loop_open"
      body = head + "for " + st.expr_str + " do"
    elsif kind == "loop_close"
      body = head + "end for"
    elsif kind == "return"
      body = head + "return " + st.var
    elsif kind == "comment"
      body = head + "" + st.note
    else
      body = head + st.expr_str
    end

    tail = st.shape
    if st.note != "" && kind != "loop_close" && kind != "comment"
      if tail == ""
        tail = "" + st.note
      else
        tail = tail + "" + st.note
      end
    end

    s = s + Toy::Card.pad_to(body, Toy::Card::SHAPE_COL, tail) + "\n"

    if kind == "loop_open"
      depth = depth + 1
    end
    n = n + 1
  end

  s
end

#step_bind(var, expr_str, shape) ⇒ Object



149
150
151
# File 'lib/toy/dev/toy_card.rb', line 149

def step_bind(var, expr_str, shape)
  @steps.push(Toy::Step.new("bind", var, expr_str, shape, ""))
end

#step_loop(header, note) ⇒ Object

‘header` is the loop header without “for”/“do” — the renderer adds those. `note` is an inline ▷ comment shown beside the line.



159
160
161
# File 'lib/toy/dev/toy_card.rb', line 159

def step_loop(header, note)
  @steps.push(Toy::Step.new("loop_open", "", header, "", note))
end

#step_loop_closeObject



163
164
165
# File 'lib/toy/dev/toy_card.rb', line 163

def step_loop_close
  @steps.push(Toy::Step.new("loop_close", "", "", "", ""))
end

#step_return(var) ⇒ Object



167
168
169
# File 'lib/toy/dev/toy_card.rb', line 167

def step_return(var)
  @steps.push(Toy::Step.new("return", var, "", "", ""))
end

#step_update(var, expr_str, shape, note) ⇒ Object



153
154
155
# File 'lib/toy/dev/toy_card.rb', line 153

def step_update(var, expr_str, shape, note)
  @steps.push(Toy::Step.new("update", var, expr_str, shape, note))
end