Skip to main content
SieR

01. Starting with

nn 개발기 1편

nn이라는 언어를 처음 고안한건 2022년 말~2023년 초 쯤이었다.

다만 그 당시에는 훨씬 범용 프로그래밍 언어적인 성격이 강했는데,

entry(x: Tensor, a: Tensor, b: Tensor) {
  let x = MatMul(x, a);
  let x = Bias(x, b);

  x
}

Rust의 서브셋같은 느낌으로 처음 구현했었다. (Python)


다만 만들다보니 파이썬을 선택한 의미가 많이 퇴색됐고, (프레임워크 호환성을 위해 선택했었다.)

파이썬 자체 문제 (익숙하지 않은 타이핑 방식, 라이브러리의 부실한 타이핑 및 문서)로 인해 사실상 개발이 많이 어려워져 버려두고 있었다. (당시 코드는 여기에서 확인할 수 있다.)

그 사이에 함수형 언어들을 접하면서 문법을 바라보는 시각도 조금 달라졌었다.

올해에는 꼭 만들어보자 하고 다짐했었는데, 올해 초에는 새 직장에도 적응해야 했고, 개인적으로 다시 잡을만한 시간이 안 나서 이제야 개발에 손을 대고 있다.


기존에 파이썬으로 구현한 경험이 있어서 새삼 느끼지만, 역시 Typescript로 구현하는 편이 훨씬 편하고 코드 정리하기도 쉬웠다.

아무튼, 이 개발기는 Typescript로 작성한 코드 기준으로 쓰일 예정이다. 개변 이전의 역사도 가끔 언급될지도 모르겠지만..


2024-09-06 3b0ea58

맨 처음 proof-of-concept는 ohm.js를 이용하여 만들어졌다.

당시 ohmjs 문법 파일은 이런 형태였다.

src/ohm/nn.ohm

SourceCode {
  Declaration = identifier SizeDecls? Arguments "=" "|>"? Expression ("|>" Expression)*
    
  Expression = TupleExpression
  
  StringLiteralExpression = string
  IdentifierExpression = identifier -- ident
    | StringLiteralExpression
  CallExpression = identifier SizeType? "(" ListOf<Expression, ","> ")" -- call
    | IdentifierExpression
  TupleExpression = Expression ( "," Expression ) + -- tuple
    | CallExpression
  
  Arguments = "(" ListOf<ArgumentDeclaration, ","> ")"
  ArgumentDeclaration = identifier ":" Type
  
  Type = identifier SizeType?
  SizeType = "[" ListOf<Size, ","> "]"
  Size = PowerSize

  SizeDecls = "[" ListOf<identifier, ","> "]"
  
  SimpleSize = number | identifier
  ParenSize = "(" Size ")" -- paren
    | SimpleSize
  AddSize = Size "+" Size -- add
    | ParenSize
  MultipleSize = Size "*" Size -- mul
    | AddSize
  PowerSize = Size "^" Size -- pow
    | MultipleSize
  
  string = singleQuoteString | doubleQuoteString 
  singleQuoteString = "'" identifier "'"
  doubleQuoteString = "\"" identifier "\""
  
  identifier = identifierName
  identifierName = identifierStart identifierPart*
  identifierStart = "_" | "$" | letter
  identifierPart = identifierStart | digit
  
  number = "1".."9" digit*
}

tree-sitter를 사용하는 지금도 큰 틀에서 벗어나지는 않는다.

test/parse/linear.nn

Linear(x: Tensor) = 
  x, Trainable('weight')
  |> MatMul(), Trainable('bias')
  |> Bias()

당시 작성했었던 Linear 코드이다.

또한 간단하게라도 Python 코드젠을 만들었는데,

위 코드에 사이즈 정보를 더해 컴파일하면

class Linear:
  def __init__(self, input, channel):
    self.weight = Tensor.zeros(input, channel)
    self.bias = Tensor.zeros(channel)

  def __call__(self, x: Tensor):
    y = MatMul(x, self.weight)
    y = Bias(y, self.bias)
    return y

와 같이 간단하게 코드젠이 이루어지는 정도까지는 만들었다.

코드젠 코드는 여기에서 읽어볼 수 있다.